Merge branch '2.3.0' into dev_2.2.3_alves_bug_fix

# Conflicts:
#	.gitignore
#	resources/web/flutter_web/flutter_bootstrap.js
#	resources/web/flutter_web/flutter_service_worker.js
#	resources/web/flutter_web/main.dart.js
#	resources/web/flutter_web/version.changelog
#	resources/web/flutter_web/version.json
#	scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.metainfo.xml
#	src/libslic3r/GCode/WipeTower2.cpp
This commit is contained in:
alves
2026-03-10 15:33:16 +08:00
63 changed files with 118797 additions and 100503 deletions
+38
View File
@@ -71,6 +71,44 @@ namespace common
#endif // _WIN32
return machineId;
}
std::string get_profile_version()
{
std::string versionFilePath = "";
#ifdef _WIN32
PWSTR pszPath = nullptr;
char* path = new char[MAX_PATH]();
size_t pathLength = 0;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pszPath);
if (SUCCEEDED(hr)) {
wcstombs_s(&pathLength, path, MAX_PATH, pszPath, MAX_PATH);
CoTaskMemFree(pszPath);
}
std::string filePath = path;
versionFilePath = filePath + "\\" + std::string("Snapmaker_Orca\\system\\Snapmaker.json");
delete[] path;
#elif __APPLE__
const char* home_env = getenv("HOME");
versionFilePath = home_env;
versionFilePath = versionFilePath + "/Library/Application Support/Snapmaker_Orca/system/Snapmaker.json";
#else
#endif
std::ifstream json_file(versionFilePath);
if (!json_file.is_open()) {
std::ifstream json_file(versionFilePath);
return "";
}
nlohmann::json json_data;
json_file >> json_data;
std::string str_version = json_data.value("version", "");
return str_version;
}
std::string get_flutter_version()
{
+2
View File
@@ -23,6 +23,8 @@ namespace common
std::string get_flutter_version();
std::string get_profile_version();
std::string getMachineId();
std::string getLocalArea();
+63 -2
View File
@@ -10,6 +10,7 @@
#include <iostream>
#include <stdexcept>
#include <string>
#include <sstream>
#include <vector>
#include "libslic3r.h"
#include "clonable_ptr.hpp"
@@ -39,6 +40,12 @@ namespace Slic3r {
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; }
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); }
inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
inline std::ostream& operator<<(std::ostream& os, const FloatOrPercent& v) {
os << v.value;
if (v.percent)
os << "%";
return os;
}
}
namespace std {
@@ -344,6 +351,9 @@ public:
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0;
// SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx
// This function is useful for applying physical extruder mapping to filament parameters
virtual void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) = 0;
// Resize the vector of values, copy the newly added values from opt_default if provided.
virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
// Clear the values vector.
@@ -419,18 +429,69 @@ public:
T v = this->values.front();
this->values.resize(i + 1, v);
}
if (rhs->type() == this->type()) {
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
// Log before assignment
std::stringstream before_ss;
before_ss << "[";
for (size_t k = 0; k < this->values.size(); ++k) {
if (k > 0) before_ss << ", ";
before_ss << this->values[k];
}
before_ss << "]";
// Log other vector
std::stringstream other_ss;
other_ss << "[";
for (size_t k = 0; k < other->values.size(); ++k) {
if (k > 0) other_ss << ", ";
other_ss << other->values[k];
}
other_ss << "]";
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
// Log after assignment
std::stringstream after_ss;
after_ss << "[";
for (size_t k = 0; k < this->values.size(); ++k) {
if (k > 0) after_ss << ", ";
after_ss << this->values[k];
}
after_ss << "]";
} else if (rhs->type() == this->scalar_type()) {
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
} else
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
// SM Orca: Copy a single element from source vector at src_idx to this vector at dst_idx
// Used for applying physical extruder mapping to filament parameters
void set_at(const ConfigOptionVectorBase* source, size_t dst_idx, size_t src_idx) override
{
auto* src_typed = dynamic_cast<const ConfigOptionVector<T>*>(source);
if (!src_typed || src_idx >= src_typed->size() || dst_idx >= this->size())
return;
// Handle nullable vectors - only copy if source value is not nil
if (this->nullable() && src_typed->nullable()) {
if (!src_typed->is_nil(src_idx)) {
this->values[dst_idx] = src_typed->values[src_idx];
}
} else if (!src_typed->nullable()) {
// Source is not nullable, always copy
this->values[dst_idx] = src_typed->values[src_idx];
}
// If source is nullable and value is nil, don't copy (keep existing value)
}
const T& get_at(size_t i) const
{
assert(! this->values.empty());
+13 -11
View File
@@ -6,13 +6,14 @@ namespace Slic3r {
double Extruder::m_share_E = 0.;
double Extruder::m_share_retracted = 0.;
Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) :
Extruder::Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder) :
m_id(id),
m_physical_extruder_id(physical_extruder_id),
m_config(config),
m_share_extruder(share_extruder)
{
reset();
// cache values that are going to be called often
m_e_per_mm3 = this->filament_flow_ratio();
m_e_per_mm3 /= this->filament_crossection();
@@ -157,24 +158,25 @@ double Extruder::filament_flow_ratio() const
}
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
// SM Orca: 回抽相关参数是挤出机属性,使用 m_physical_extruder_id
double Extruder::retract_before_wipe() const
{
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_physical_extruder_id) * 0.01));
}
double Extruder::retraction_length() const
{
return m_config->retraction_length.get_at(m_id);
return m_config->retraction_length.get_at(m_physical_extruder_id);
}
double Extruder::retract_lift() const
{
return m_config->z_hop.get_at(m_id);
return m_config->z_hop.get_at(m_physical_extruder_id);
}
int Extruder::retract_speed() const
{
return int(floor(m_config->retraction_speed.get_at(m_id)+0.5));
return int(floor(m_config->retraction_speed.get_at(m_physical_extruder_id)+0.5));
}
bool Extruder::use_firmware_retraction() const
@@ -184,28 +186,28 @@ bool Extruder::use_firmware_retraction() const
int Extruder::deretract_speed() const
{
int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5));
int speed = int(floor(m_config->deretraction_speed.get_at(m_physical_extruder_id)+0.5));
return (speed > 0) ? speed : this->retract_speed();
}
double Extruder::retract_restart_extra() const
{
return m_config->retract_restart_extra.get_at(m_id);
return m_config->retract_restart_extra.get_at(m_physical_extruder_id);
}
double Extruder::retract_length_toolchange() const
{
return m_config->retract_length_toolchange.get_at(m_id);
return m_config->retract_length_toolchange.get_at(m_physical_extruder_id);
}
double Extruder::retract_restart_extra_toolchange() const
{
return m_config->retract_restart_extra_toolchange.get_at(m_id);
return m_config->retract_restart_extra_toolchange.get_at(m_physical_extruder_id);
}
double Extruder::travel_slope() const
{
return m_config->travel_slope.get_at(m_id) * PI / 180;
return m_config->travel_slope.get_at(m_physical_extruder_id) * PI / 180;
}
}
+8 -3
View File
@@ -11,7 +11,8 @@ class GCodeConfig;
class Extruder
{
public:
Extruder(unsigned int id, GCodeConfig *config, bool share_extruder);
// SM Orca: 添加 physical_extruder_id 参数用于支持耗材-挤出机映射
Extruder(unsigned int id, unsigned int physical_extruder_id, GCodeConfig *config, bool share_extruder);
virtual ~Extruder() {}
void reset() {
@@ -28,6 +29,8 @@ public:
}
unsigned int id() const { return m_id; }
// SM Orca: 获取物理挤出机ID
unsigned int physical_extruder_id() const { return m_physical_extruder_id; }
double extrude(double dE);
double retract(double length, double restart_extra);
@@ -75,12 +78,14 @@ public:
private:
// Private constructor to create a key for a search in std::set.
Extruder(unsigned int id) : m_id(id) {}
Extruder(unsigned int id) : m_id(id), m_physical_extruder_id(id) {}
// Reference to GCodeWriter instance owned by GCodeWriter.
GCodeConfig *m_config;
// Print-wide global ID of this extruder.
// Print-wide global ID of this extruder (filament index).
unsigned int m_id;
// SM Orca: 物理挤出机ID,用于查询挤出机属性(温度、回抽等)
unsigned int m_physical_extruder_id;
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
double m_E;
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
+35 -4
View File
@@ -213,42 +213,73 @@ double Flow::mm3_per_mm() const
Flow support_material_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)),
float(object->print()->config().nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
//BBS
Flow support_transition_flow(const PrintObject* object)
{
//BBS: support transition of tree support is bridge flow
float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1));
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
float dmr = float(object->print()->config().nozzle_diameter.get_at(physical_extruder));
return Flow::bridging_flow(dmr, dmr);
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
const PrintConfig &print_config = object->print()->config();
// SM Orca: 日志 - 配置数组访问边界检查
size_t array_size = print_config.nozzle_diameter.values.size();
const auto &width = (print_config.initial_layer_line_width.value > 0) ? print_config.initial_layer_line_width : object->config().support_line_width;
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().line_width,
float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)),
float(print_config.nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value));
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
{
// SM Orca: 使用物理挤出机的喷嘴直径
int filament_idx = object->config().support_interface_filament - 1;
int physical_extruder = object->print()->get_physical_extruder(filament_idx);
// SM Orca: 日志 - 配置数组访问边界检查
const auto& nozzle_diameter_config = object->print()->config().nozzle_diameter;
size_t array_size = nozzle_diameter_config.values.size();
return Flow::new_from_config_width(
frSupportMaterialInterface,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)),
float(object->print()->config().nozzle_diameter.get_at(physical_extruder)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
+2774 -2682
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -114,7 +114,7 @@ private:
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
std::string post_process_wipe_tower_moves(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
@@ -197,6 +197,13 @@ public:
//BBS: set offset for gcode writer
void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
// SM Orca: Set filament-extruder mapping
void set_filament_extruder_map(const std::unordered_map<int, int>& map) {
m_writer.set_filament_extruder_map(map);
m_processor.set_filament_extruder_map(map);
if (m_cooling_buffer) m_cooling_buffer->set_filament_extruder_map(map);
}
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);
@@ -482,8 +482,11 @@ static inline float get_default_perimeter_spacing(const PrintObject &print_objec
std::vector<unsigned int> printing_extruders = print_object.object_extruders();
assert(!printing_extruders.empty());
float avg_extruder = 0;
for(unsigned int extruder_id : printing_extruders)
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)));
for(unsigned int extruder_id : printing_extruders) {
// SM Orca: nozzle_diameter是物理挤出机参数,使用physical_extruder_id访问
int physical_extruder_id = print_object.print()->get_physical_extruder(extruder_id);
avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(physical_extruder_id)));
}
avg_extruder /= printing_extruders.size();
return avg_extruder;
}
+8 -5
View File
@@ -340,12 +340,14 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
for (size_t i = 0; i < m_extruder_ids.size(); ++ i) {
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
unsigned int extruder_id = m_extruder_ids[i];
// SM Orca: 冷却参数都是物理挤出机参数(无耗材覆盖),使用physical_extruder_id访问
int physical_extruder_id = get_physical_extruder(extruder_id);
adj.extruder_id = extruder_id;
adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id);
adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id));
adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id));
adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(physical_extruder_id);
adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(physical_extruder_id));
adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(physical_extruder_id));
// ORCA: To enable dont slow down external perimeters feature per filament (extruder)
adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id);
adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(physical_extruder_id);
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
}
@@ -731,7 +733,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
&supp_interface_fan_control, &supp_interface_fan_speed,
&ironing_fan_control, &ironing_fan_speed
](bool immediately_apply) {
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
// SM Orca: 风扇参数都是物理挤出机参数(无耗材覆盖),使用physical_extruder_id访问
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(get_physical_extruder(m_current_extruder))
float fan_min_speed = EXTRUDER_CONFIG(fan_min_speed);
float fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0;
//BBS
+9
View File
@@ -27,6 +27,13 @@ public:
void reset(const Vec3d &position);
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
// SM Orca: Set filament to physical extruder mapping for correct parameter access
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_filament_extruder_map = map; }
// SM Orca: Get physical extruder ID from filament ID
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
return (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
}
private:
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
@@ -55,6 +62,8 @@ private:
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
const PrintConfig &m_config;
unsigned int m_current_extruder;
// SM Orca: Filament to physical extruder mapping for correct parameter access
std::unordered_map<int, int> m_filament_extruder_map;
//BBS: current fan speed
int m_current_fan_speed;
};
+465 -72
View File
@@ -94,7 +94,6 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
" PA_CHANGE:"
};
const std::string GCodeProcessor::Flush_Start_Tag = " FLUSH_START";
const std::string GCodeProcessor::Flush_End_Tag = " FLUSH_END";
@@ -397,7 +396,6 @@ void GCodeProcessor::TimeProcessor::reset()
filament_unload_times = 0.0f;
machine_tool_change_time = 0.0f;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
machines[i].reset();
}
@@ -457,7 +455,6 @@ void GCodeProcessor::UsedFilaments::process_color_change_cache()
}
}
void GCodeProcessor::UsedFilaments::process_total_volume_cache(GCodeProcessor* processor)
{
size_t active_extruder_id = processor->m_extruder_id;
@@ -526,9 +523,17 @@ void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor
if (role_cache != 0.0f) {
std::pair<double, double> filament = { 0.0f, 0.0f };
double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]);
float diameter = (static_cast<size_t>(processor->m_extruder_id) < processor->m_result.filament_diameters.size())
? processor->m_result.filament_diameters[processor->m_extruder_id]
: processor->m_result.filament_diameters.back();
float density = (static_cast<size_t>(processor->m_extruder_id) < processor->m_result.filament_densities.size())
? processor->m_result.filament_densities[processor->m_extruder_id]
: processor->m_result.filament_densities.back();
double s = PI * sqr(0.5 * diameter);
filament.first = role_cache / s * 0.001;
filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001;
filament.second = role_cache * density * 0.001;
ExtrusionRole active_role = processor->m_extrusion_role;
if (filaments_per_role.find(active_role) != filaments_per_role.end()) {
@@ -556,6 +561,20 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
size_t saved_count = extruders_count;
if (saved_count == 0 || saved_count > 256) {
// 尝试从已有数组大小推断(优先使用filament_diameters的大小)
if (!filament_diameters.empty() && filament_diameters.size() <= 256) {
saved_count = filament_diameters.size();
<< saved_count;
} else {
// 对于只有少量耗材的用户,稍微多分配一些内存影响很小
saved_count = 16;
<< saved_count << " (was " << extruders_count << ", array size was " << filament_diameters.size() << ")";
}
}
moves = std::vector<GCodeProcessorResult::MoveVertex>();
printable_area = Pointfs();
//BBS: add bed exclude area
@@ -567,10 +586,19 @@ void GCodeProcessorResult::reset() {
timelapse_warning_code = 0;
printable_height = 0.0f;
settings_ids.reset();
extruders_count = 0;
extruders_count = saved_count;
extruder_colors = std::vector<std::string>();
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
filament_diameters = std::vector<float>(saved_count, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(saved_count, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(saved_count, DEFAULT_FILAMENT_COST);
required_nozzle_HRC = std::vector<int>(saved_count, DEFAULT_FILAMENT_HRC);
filament_vitrification_temperature = std::vector<int>(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
<< " (original extruders_count=" << (saved_count == extruders_count ? "preserved" : "inferred")
<< ", this=" << this << ")";
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
time = 0;
@@ -583,6 +611,18 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
size_t saved_count = extruders_count;
if (saved_count == 0 || saved_count > 256) {
// 尝试从已有数组大小推断(优先使用filament_diameters的大小)
if (!filament_diameters.empty() && filament_diameters.size() <= 256) {
saved_count = filament_diameters.size();
} else {
// 对于只有少量耗材的用户,稍微多分配一些内存影响很小
saved_count = 16;
}
}
moves.clear();
lines_ends.clear();
printable_area = Pointfs();
@@ -596,13 +636,17 @@ void GCodeProcessorResult::reset() {
timelapse_warning_code = 0;
printable_height = 0.0f;
settings_ids.reset();
extruders_count = 0;
backtrace_enabled = false;
extruder_colors = std::vector<std::string>();
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
required_nozzle_HRC = std::vector<int>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_HRC);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST);
extruders_count = saved_count;
filament_diameters = std::vector<float>(saved_count, DEFAULT_FILAMENT_DIAMETER);
required_nozzle_HRC = std::vector<int>(saved_count, DEFAULT_FILAMENT_HRC);
filament_densities = std::vector<float>(saved_count, DEFAULT_FILAMENT_DENSITY);
filament_costs = std::vector<float>(saved_count, DEFAULT_FILAMENT_COST);
filament_vitrification_temperature = std::vector<int>(saved_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
bed_match_result = BedMatchResult(true);
@@ -719,6 +763,40 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_preheat_steps = 1;
m_result.backtrace_enabled = m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && extruders_count > 1));
size_t physical_extruder_count = config.extruder_offset.values.size();
// 验证各配置数组大小
if (config.filament_density.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_density has "
<< config.filament_density.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
if (config.filament_cost.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: filament_cost has "
<< config.filament_cost.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
if (config.nozzle_temperature.values.size() < extruders_count) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: nozzle_temperature has "
<< config.nozzle_temperature.values.size() << " values, expected " << extruders_count
<< " (will use fallback values)";
}
// 验证映射表有效性
if (!m_filament_extruder_map.empty()) {
for (size_t i = 0; i < extruders_count; ++i) {
int physical_extruder = get_physical_extruder(i);
if (physical_extruder < 0 ||
physical_extruder >= static_cast<int>(physical_extruder_count)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i
<< " maps to invalid physical extruder " << physical_extruder
<< " (valid range: 0-" << (physical_extruder_count - 1) << ")";
}
}
}
m_extruder_offsets.resize(extruders_count);
m_extruder_colors.resize(extruders_count);
m_result.filament_diameters.resize(extruders_count);
@@ -731,20 +809,94 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_extruder_temps_first_layer_config.resize(extruders_count);
m_result.nozzle_hrc = static_cast<int>(config.nozzle_hrc.getInt());
m_result.nozzle_type = config.nozzle_type;
size_t diameter_count = config.filament_diameter.values.size();
size_t density_count = config.filament_density.values.size();
size_t cost_count = config.filament_cost.values.size();
size_t temp_initial_count = config.nozzle_temperature_initial_layer.values.size();
size_t temp_count = config.nozzle_temperature.values.size();
size_t hrc_count = config.required_nozzle_HRC.values.size();
size_t vitrification_count = config.temperature_vitrification.values.size();
for (size_t i = 0; i < extruders_count; ++ i) {
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f);
int physical_extruder = get_physical_extruder(i);
if (physical_extruder < 0 || physical_extruder >= static_cast<int>(physical_extruder_count)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament " << i
<< " maps to invalid physical extruder " << physical_extruder
<< " (valid range: 0-" << (physical_extruder_count - 1) << ")";
// 使用filament index作为fallback1:1映射),如果也越界则使用0
physical_extruder = (i < physical_extruder_count) ? static_cast<int>(i) : 0;
BOOST_LOG_TRIVIAL(error) << " SM Orca: Using fallback physical extruder " << physical_extruder;
}
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(physical_extruder).cast<float>().eval(), 0.f);
m_extruder_colors[i] = static_cast<unsigned char>(i);
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.nozzle_temperature_initial_layer.get_at(i));
m_extruder_temps_config[i] = static_cast<int>(config.nozzle_temperature.get_at(i));
// 温度是挤出机属性,使用 physical_extruder 而不是 filament index
if (physical_extruder < static_cast<int>(temp_initial_count)) {
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.nozzle_temperature_initial_layer.get_at(physical_extruder));
} else {
int fallback = temp_initial_count > 0 ?
static_cast<int>(config.nozzle_temperature_initial_layer.get_at(temp_initial_count - 1)) : 210;
m_extruder_temps_first_layer_config[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") initial layer temperature not configured, using " << fallback;
}
if (physical_extruder < static_cast<int>(temp_count)) {
m_extruder_temps_config[i] = static_cast<int>(config.nozzle_temperature.get_at(physical_extruder));
} else {
int fallback = temp_count > 0 ?
static_cast<int>(config.nozzle_temperature.get_at(temp_count - 1)) : 210;
m_extruder_temps_config[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " (physical extruder " << physical_extruder << ") temperature not configured, using " << fallback;
}
if (m_extruder_temps_config[i] == 0) {
// This means the value should be ignored and first layer temp should be used.
m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i];
}
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
m_result.required_nozzle_HRC[i] = static_cast<int>(config.required_nozzle_HRC.get_at(i));
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
m_result.filament_vitrification_temperature[i] = static_cast<float>(config.temperature_vitrification.get_at(i));
m_result.filament_costs[i] = static_cast<float>(config.filament_cost.get_at(i));
if (i < diameter_count) {
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
} else {
float fallback = diameter_count > 0 ?
static_cast<float>(config.filament_diameter.get_at(diameter_count - 1)) : 1.75f;
m_result.filament_diameters[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " diameter not configured, using " << fallback << "mm";
}
if (i < hrc_count) {
m_result.required_nozzle_HRC[i] = static_cast<int>(config.required_nozzle_HRC.get_at(i));
} else {
int fallback = hrc_count > 0 ?
static_cast<int>(config.required_nozzle_HRC.get_at(hrc_count - 1)) : 0;
m_result.required_nozzle_HRC[i] = fallback;
}
if (i < density_count) {
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
} else {
float fallback = density_count > 0 ?
static_cast<float>(config.filament_density.get_at(density_count - 1)) : 1.25f;
m_result.filament_densities[i] = fallback;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " density not configured, using " << fallback << " g/cm³";
}
if (i < vitrification_count) {
m_result.filament_vitrification_temperature[i] = static_cast<int>(config.temperature_vitrification.get_at(i));
} else {
int fallback = vitrification_count > 0 ?
static_cast<int>(config.temperature_vitrification.get_at(vitrification_count - 1)) : 0;
m_result.filament_vitrification_temperature[i] = fallback;
}
if (i < cost_count) {
m_result.filament_costs[i] = static_cast<float>(config.filament_cost.get_at(i));
} else {
m_result.filament_costs[i] = 0.0f;
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Filament " << i << " cost not configured, using 0.0";
}
}
if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper || m_flavor == gcfRepRapFirmware) {
@@ -858,24 +1010,35 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloats* filament_diameters = config.option<ConfigOptionFloats>("filament_diameter");
if (filament_diameters != nullptr) {
m_result.filament_diameters.clear();
m_result.filament_diameters.resize(filament_diameters->values.size());
for (size_t i = 0; i < filament_diameters->values.size(); ++i) {
size_t config_size = filament_diameters->values.size();
if (m_result.filament_diameters.size() < m_result.extruders_count) {
m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER);
}
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_diameters[i] = static_cast<float>(filament_diameters->values[i]);
}
if (config_size > 0 && config_size < m_result.extruders_count) {
float last_value = static_cast<float>(filament_diameters->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_diameters[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " diameter not in config, using last value " << last_value << "mm";
}
}
}
if (m_result.filament_diameters.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) {
m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER);
}
m_result.filament_diameters.resize(m_result.extruders_count, DEFAULT_FILAMENT_DIAMETER);
}
const ConfigOptionInts *filament_HRC = config.option<ConfigOptionInts>("required_nozzle_HRC");
if (filament_HRC != nullptr) {
m_result.required_nozzle_HRC.clear();
m_result.required_nozzle_HRC.resize(filament_HRC->values.size());
for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast<float>(filament_HRC->values[i]); }
for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast<int>(filament_HRC->values[i]); }
}
if (m_result.required_nozzle_HRC.size() < m_result.extruders_count) {
@@ -885,43 +1048,75 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloats* filament_densities = config.option<ConfigOptionFloats>("filament_density");
if (filament_densities != nullptr) {
m_result.filament_densities.clear();
m_result.filament_densities.resize(filament_densities->values.size());
for (size_t i = 0; i < filament_densities->values.size(); ++i) {
size_t config_size = filament_densities->values.size();
if (m_result.filament_densities.size() < m_result.extruders_count) {
m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY);
}
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_densities[i] = static_cast<float>(filament_densities->values[i]);
}
if (config_size > 0 && config_size < m_result.extruders_count) {
float last_value = static_cast<float>(filament_densities->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_densities[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " density not in config, using last value " << last_value << " g/cm³";
}
}
}
if (m_result.filament_densities.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) {
m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY);
}
m_result.filament_densities.resize(m_result.extruders_count, DEFAULT_FILAMENT_DENSITY);
}
//BBS
const ConfigOptionFloats* filament_costs = config.option<ConfigOptionFloats>("filament_cost");
if (filament_costs != nullptr) {
m_result.filament_costs.clear();
m_result.filament_costs.resize(filament_costs->values.size());
for (size_t i = 0; i < filament_costs->values.size(); ++i)
m_result.filament_costs[i]=static_cast<float>(filament_costs->values[i]);
size_t config_size = filament_costs->values.size();
if (m_result.filament_costs.size() < m_result.extruders_count) {
m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST);
}
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i)
m_result.filament_costs[i] = static_cast<float>(filament_costs->values[i]);
if (config_size < m_result.extruders_count) {
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_costs[i] = DEFAULT_FILAMENT_COST;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " cost not in config, using default " << DEFAULT_FILAMENT_COST;
}
}
}
for (size_t i = m_result.filament_costs.size(); i < m_result.extruders_count; ++i) {
m_result.filament_costs.emplace_back(DEFAULT_FILAMENT_COST);
if (m_result.filament_costs.size() < m_result.extruders_count) {
m_result.filament_costs.resize(m_result.extruders_count, DEFAULT_FILAMENT_COST);
}
//BBS
const ConfigOptionInts* filament_vitrification_temperature = config.option<ConfigOptionInts>("temperature_vitrification");
if (filament_vitrification_temperature != nullptr) {
m_result.filament_vitrification_temperature.clear();
m_result.filament_vitrification_temperature.resize(filament_vitrification_temperature->values.size());
for (size_t i = 0; i < filament_vitrification_temperature->values.size(); ++i) {
size_t config_size = filament_vitrification_temperature->values.size();
if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) {
m_result.filament_vitrification_temperature.resize(m_result.extruders_count, DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
}
for (size_t i = 0; i < config_size && i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature[i] = static_cast<int>(filament_vitrification_temperature->values[i]);
}
}
if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) {
for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature.emplace_back(DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
if (config_size > 0 && config_size < m_result.extruders_count) {
int last_value = static_cast<int>(filament_vitrification_temperature->values[config_size - 1]);
for (size_t i = config_size; i < m_result.extruders_count; ++i) {
m_result.filament_vitrification_temperature[i] = last_value;
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << i
<< " vitrification temperature not in config, using last value " << last_value;
}
}
}
@@ -937,17 +1132,32 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
}
}
else {
m_extruder_offsets.resize(extruder_offset->values.size());
for (size_t i = 0; i < extruder_offset->values.size(); ++i) {
size_t physical_count = extruder_offset->values.size();
if (m_extruder_offsets.size() < m_result.extruders_count) {
m_extruder_offsets.resize(m_result.extruders_count, DEFAULT_EXTRUDER_OFFSET);
}
// 只更新物理挤出机的offset
for (size_t i = 0; i < physical_count && i < m_extruder_offsets.size(); ++i) {
Vec2f offset = extruder_offset->values[i].cast<float>();
m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
}
}
}
if (m_extruder_offsets.size() < m_result.extruders_count) {
size_t physical_count = m_extruder_offsets.size();
for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) {
m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET);
int physical_extruder = get_physical_extruder(i);
// 如果映射的物理挤出机索引在有效范围内,复用它的offset
if (physical_extruder >= 0 && physical_extruder < static_cast<int>(physical_count)) {
m_extruder_offsets.emplace_back(m_extruder_offsets[physical_extruder]);
BOOST_LOG_TRIVIAL(debug) << "Filament " << i << " using offset from physical extruder " << physical_extruder;
} else {
// 否则使用默认offset
m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET);
BOOST_LOG_TRIVIAL(warning) << "Filament " << i << " using default offset (physical extruder " << physical_extruder << " out of range)";
}
}
}
@@ -991,7 +1201,6 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (machine_tool_change_time != nullptr)
m_time_processor.machine_tool_change_time = static_cast<float>(machine_tool_change_time->value);
if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper) {
const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("machine_max_acceleration_x");
if (machine_max_acceleration_x != nullptr)
@@ -1053,7 +1262,6 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (machine_max_acceleration_retracting != nullptr)
m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values;
// Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead.
const ConfigOptionFloats* machine_max_acceleration_travel = config.option<ConfigOptionFloats>(m_flavor == gcfMarlinLegacy || m_flavor == gcfKlipper
? "machine_max_acceleration_extruding"
@@ -1061,7 +1269,6 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (machine_max_acceleration_travel != nullptr)
m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values;
const ConfigOptionFloats* machine_min_extruding_rate = config.option<ConfigOptionFloats>("machine_min_extruding_rate");
if (machine_min_extruding_rate != nullptr)
m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values;
@@ -1113,10 +1320,32 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (bed_type != nullptr)
m_result.bed_type = (BedType)bed_type->value;
const ConfigOptionFloat* z_offset = config.option<ConfigOptionFloat>("z_offset");
if (z_offset != nullptr)
m_z_offset = z_offset->value;
bool arrays_valid = true;
if (m_result.filament_diameters.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_diameters size mismatch: "
<< m_result.filament_diameters.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_densities.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_densities size mismatch: "
<< m_result.filament_densities.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_costs.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_costs size mismatch: "
<< m_result.filament_costs.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
if (m_result.filament_vitrification_temperature.size() != m_result.extruders_count) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: CRITICAL - filament_vitrification_temperature size mismatch: "
<< m_result.filament_vitrification_temperature.size() << " != " << m_result.extruders_count;
arrays_valid = false;
}
}
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
@@ -1556,6 +1785,22 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
// update start position
m_start_position = m_end_position;
if (std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) ||
std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) ||
std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Detected invalid m_start_position at line " << m_line_id
<< " extruder=" << static_cast<int>(m_extruder_id)
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")";
// 重置为原点,防止污染继续传播
m_start_position[X] = std::isnan(m_start_position[X]) || std::isinf(m_start_position[X]) ? 0.0f : m_start_position[X];
m_start_position[Y] = std::isnan(m_start_position[Y]) || std::isinf(m_start_position[Y]) ? 0.0f : m_start_position[Y];
m_start_position[Z] = std::isnan(m_start_position[Z]) || std::isinf(m_start_position[Z]) ? 0.0f : m_start_position[Z];
m_end_position[X] = std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ? 0.0f : m_end_position[X];
m_end_position[Y] = std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ? 0.0f : m_end_position[Y];
m_end_position[Z] = std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z]) ? 0.0f : m_end_position[Z];
}
const std::string_view cmd = line.cmd();
if (m_flavor == gcfKlipper)
{
@@ -2635,6 +2880,21 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
m_end_position[a] = absolute_position((Axis)a, line);
}
if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ||
std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ||
std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G1 processing for extruder " << static_cast<int>(m_extruder_id)
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z)
<< " X_value=" << (line.has(X) ? line.value(X) : 0.0f)
<< " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f)
<< " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f)
<< " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute")
<< " units=" << (m_units == EUnits::Inches ? "inches" : "mm");
}
// updates feedrate from line, if present
if (line.has_f())
m_feedrate = line.f() * MMMIN_TO_MMSEC;
@@ -2698,13 +2958,32 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
else if (m_extrusion_role == erExternalPerimeter)
// cross section: rectangle
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height);
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone)
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) {
float diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size())
? m_result.filament_diameters[m_extruder_id]
: m_result.filament_diameters.back();
float ratio = delta_pos[E] / delta_xyz;
if (ratio < 0.0f) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio
<< ") for extruder " << m_extruder_id << ", using absolute value";
ratio = std::abs(ratio);
}
// cross section: circle
m_width = static_cast<float>(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
m_width = diameter * std::sqrt(ratio);
}
else
// cross section: rectangle + 2 semicircles
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width
<< " for extruder " << m_extruder_id
<< " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (m_width == 0.0f)
m_width = DEFAULT_TOOLPATH_WIDTH;
@@ -2913,7 +3192,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line, const std::o
// axis reversal
std::max(-v_exit, v_entry));
float axis_max_jerk = get_axis_max_jerk(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
if (jerk > axis_max_jerk) {
v_factor *= axis_max_jerk / jerk;
@@ -3086,6 +3364,22 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
for (unsigned char a = X; a <= E; ++a) {
m_end_position[a] = absolute_position((Axis)a, line);
}
if (std::isnan(m_end_position[X]) || std::isinf(m_end_position[X]) ||
std::isnan(m_end_position[Y]) || std::isinf(m_end_position[Y]) ||
std::isnan(m_end_position[Z]) || std::isinf(m_end_position[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_end_position after G2/G3 processing for extruder " << static_cast<int>(m_extruder_id)
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " m_start_position=(" << m_start_position[X] << ", " << m_start_position[Y] << ", " << m_start_position[Z] << ")"
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " has_X=" << line.has(X) << " has_Y=" << line.has(Y) << " has_Z=" << line.has(Z)
<< " X_value=" << (line.has(X) ? line.value(X) : 0.0f)
<< " Y_value=" << (line.has(Y) ? line.value(Y) : 0.0f)
<< " Z_value=" << (line.has(Z) ? line.value(Z) : 0.0f)
<< " positioning=" << (m_global_positioning_type == EPositioningType::Relative ? "relative" : "absolute")
<< " units=" << (m_units == EUnits::Inches ? "inches" : "mm");
}
//BBS: G2 G3 line but has no I and J axis, invalid G code format
if (!line.has(I) && !line.has(J))
return;
@@ -3130,7 +3424,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
EMoveType type = move_type(delta_pos[E]);
const float delta_xyz = std::sqrt(sqr(arc_length) + sqr(delta_pos[Z]));
m_travel_dist = delta_xyz;
if (type == EMoveType::Extrude) {
@@ -3178,13 +3471,32 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
else if (m_extrusion_role == erExternalPerimeter)
//BBS: cross section: rectangle
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height);
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone)
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erInternalBridgeInfill || m_extrusion_role == erNone) {
float diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size())
? m_result.filament_diameters[m_extruder_id]
: m_result.filament_diameters.back();
float ratio = delta_pos[E] / delta_xyz;
if (ratio < 0.0f) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Negative E/XYZ ratio (" << ratio
<< ") for extruder " << m_extruder_id << ", using absolute value";
ratio = std::abs(ratio);
}
//BBS: cross section: circle
m_width = static_cast<float>(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
m_width = diameter * std::sqrt(ratio);
}
else
//BBS: cross section: rectangle + 2 semicircles
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid width calculated: " << m_width
<< " for extruder " << m_extruder_id
<< " (E=" << delta_pos[E] << ", XYZ=" << delta_xyz << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (m_width == 0.0f)
m_width = DEFAULT_TOOLPATH_WIDTH;
@@ -3348,7 +3660,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
//BBS: axis reversal
std::max(-v_exit, v_entry));
float axis_max_jerk = get_axis_max_jerk(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
if (jerk > axis_max_jerk) {
v_factor *= axis_max_jerk / jerk;
@@ -3574,12 +3885,25 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
simulate_st_synchronize();
if (!any_found && !line.has_unknown_axis()) {
// The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
// The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
// where G92 A0 B0 is called although the extruder axis is till E.
for (unsigned char a = X; a <= E; ++a) {
m_origin[a] = m_end_position[a];
}
}
if (std::isnan(m_origin[X]) || std::isinf(m_origin[X]) ||
std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) ||
std::isnan(m_origin[Z]) || std::isinf(m_origin[Z])) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid m_origin after G92 processing"
<< " extruder=" << static_cast<int>(m_extruder_id)
<< " m_origin=(" << m_origin[X] << ", " << m_origin[Y] << ", " << m_origin[Z] << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")";
// 重置为0,防止污染继续传播
m_origin[X] = std::isnan(m_origin[X]) || std::isinf(m_origin[X]) ? 0.0f : m_origin[X];
m_origin[Y] = std::isnan(m_origin[Y]) || std::isinf(m_origin[Y]) ? 0.0f : m_origin[Y];
m_origin[Z] = std::isnan(m_origin[Z]) || std::isinf(m_origin[Z]) ? 0.0f : m_origin[Z];
}
}
void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line)
@@ -3709,7 +4033,6 @@ void GCodeProcessor::process_M191(const GCodeReader::GCodeLine& line)
simulate_st_synchronize(wait_chamber_temp_time);
}
void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
{
// see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
@@ -4045,17 +4368,54 @@ void GCodeProcessor::run_post_process()
double filament_total_cost = 0.0;
for (const auto& [id, volume] : m_result.print_statistics.total_volumes_per_extruder) {
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
if (id >= m_result.filament_diameters.size() ||
id >= m_result.filament_densities.size() ||
id >= m_result.filament_costs.size()) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Filament index " << id << " out of bounds (sizes: "
<< "diameter=" << m_result.filament_diameters.size()
<< ", density=" << m_result.filament_densities.size()
<< ", cost=" << m_result.filament_costs.size() << "), skipping cost calculation";
continue;
}
double diameter = m_result.filament_diameters[id];
double density = m_result.filament_densities[id];
double cost = m_result.filament_costs[id];
if (diameter <= 0.0 || std::isnan(diameter)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament diameter " << diameter
<< " for filament " << id << ", using default 1.75mm";
diameter = 1.75;
}
if (density <= 0.0 || std::isnan(density)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament density " << density
<< " for filament " << id << ", using default 1.25 g/cm³";
density = 1.25;
}
if (cost < 0.0 || std::isnan(cost)) {
BOOST_LOG_TRIVIAL(warning) << "SM Orca: Invalid filament cost " << cost
<< " for filament " << id << ", using 0.0";
cost = 0.0;
}
double cross_section = M_PI * sqr(0.5 * diameter);
filament_mm[id] = volume / cross_section;
filament_cm3[id] = volume * 0.001;
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
filament_cost[id] = filament_g[id] * double(m_result.filament_costs[id]) * 0.001;
filament_g[id] = filament_cm3[id] * density;
filament_cost[id] = filament_g[id] * cost * 0.001;
filament_total_g += filament_g[id];
filament_total_cost += filament_cost[id];
BOOST_LOG_TRIVIAL(debug) << "SM Orca: Filament " << id
<< " - volume: " << volume << "mm³, length: " << filament_mm[id]
<< "mm, weight: " << filament_g[id] << "g, cost: " << filament_cost[id];
}
double total_g_wipe_tower = m_print->print_statistics().total_wipe_tower_filament;
auto time_in_minutes = [](float time_in_seconds) {
assert(time_in_seconds >= 0.f);
return int((time_in_seconds + 0.5f) / 60.0f);
@@ -4183,7 +4543,6 @@ void GCodeProcessor::run_post_process()
size_t m_times_cache_id{ 0 };
size_t m_out_file_pos{ 0 };
public:
ExportLines(EWriteType type,
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines)
@@ -4753,7 +5112,6 @@ void GCodeProcessor::run_post_process()
export_lines.flush(out, m_result, out_path);
out.close();
in.close();
@@ -4771,6 +5129,10 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
m_line_id + 1 :
((type == EMoveType::Seam) ? m_last_line_id : m_line_id);
Vec3f extruder_offset = (static_cast<size_t>(m_extruder_id) < m_extruder_offsets.size())
? m_extruder_offsets[m_extruder_id]
: Vec3f(0.0f, 0.0f, 0.0f);
//BBS: apply plate's and extruder's offset to arc interpolation points
if (path_type == EMovePathType::Arc_move_cw ||
path_type == EMovePathType::Arc_move_ccw) {
@@ -4779,7 +5141,38 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
Vec3f(m_interpolation_points[i].x() + m_x_offset,
m_interpolation_points[i].y() + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) +
m_extruder_offsets[m_extruder_id];
extruder_offset;
}
if (std::isnan(m_width) || std::isinf(m_width)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid width: " << m_width
<< " (extruder " << m_extruder_id << ")";
m_width = DEFAULT_TOOLPATH_WIDTH;
}
if (std::isnan(m_height) || std::isinf(m_height)) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Blocking invalid height: " << m_height
<< " (extruder " << m_extruder_id << ")";
m_height = DEFAULT_TOOLPATH_HEIGHT;
}
Vec3f final_position = Vec3f(m_end_position[X] + m_x_offset,
m_end_position[Y] + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset)
+ extruder_offset;
if (std::isnan(final_position.x()) || std::isinf(final_position.x()) ||
std::isnan(final_position.y()) || std::isinf(final_position.y()) ||
std::isnan(final_position.z()) || std::isinf(final_position.z())) {
BOOST_LOG_TRIVIAL(error) << "SM Orca: Invalid position calculated for extruder " << static_cast<int>(m_extruder_id)
<< " position=(" << final_position.x() << ", " << final_position.y() << ", " << final_position.z() << ")"
<< " m_end_position=(" << m_end_position[X] << ", " << m_end_position[Y] << ", " << m_end_position[Z] << ")"
<< " offset=(" << m_x_offset << ", " << m_y_offset << ", " << m_z_offset << ")"
<< " extruder_offset=(" << extruder_offset.x() << ", " << extruder_offset.y() << ", " << extruder_offset.z() << ")";
// 使用不带offset的position作为fallback
final_position = Vec3f(m_end_position[X] + m_x_offset,
m_end_position[Y] + m_y_offset,
m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z] - m_z_offset);
}
m_result.moves.push_back({
@@ -4789,7 +5182,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
m_extruder_id,
m_cp_color.current,
//BBS: add plate's offset to the rendering vertices
Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]- m_z_offset) + m_extruder_offsets[m_extruder_id],
final_position,
static_cast<float>(m_end_position[E] - m_start_position[E]),
m_feedrate,
m_width,
+13
View File
@@ -14,6 +14,7 @@
#include <string>
#include <string_view>
#include <optional>
#include <unordered_map>
namespace Slic3r {
@@ -677,6 +678,8 @@ class Print;
EPositioningType m_global_positioning_type;
EPositioningType m_e_local_positioning_type;
std::vector<Vec3f> m_extruder_offsets;
// SM Orca: 耗材到物理挤出机的映射
std::unordered_map<int, int> m_filament_extruder_map;
GCodeFlavor m_flavor;
float m_nozzle_volume;
AxisCoords m_start_position; // mm
@@ -776,6 +779,16 @@ class Print;
void apply_config(const PrintConfig& config);
void set_print(Print* print) { m_print = print; }
// SM Orca: 设置耗材到物理挤出机的映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) {
m_filament_extruder_map = map;
}
// SM Orca: 获取物理挤出机ID(根据耗材索引)
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id = (it != m_filament_extruder_map.end()) ? it->second : filament_idx;
return physical_extruder_id;
}
void enable_stealth_time_estimator(bool enabled);
bool is_stealth_time_estimator_enabled() const {
return m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled;
+16 -6
View File
@@ -668,18 +668,20 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
void WipeTower::set_extruder(size_t idx, int physical_extruder, const PrintConfig& config)
{
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
m_filpar.push_back(FilamentParameters());
// SM Orca: 耗材属性使用 idx (filament index)
m_filpar[idx].material = config.filament_type.get_at(idx);
// m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1));
// BBS
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx);
m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx);
// SM Orca: 温度是挤出机属性,使用 physical_extruder
m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(physical_extruder);
m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(physical_extruder);
// If this is a single extruder MM printer, we will use all the SE-specific config values.
// Otherwise, the defaults will be used to turn off the SE stuff.
@@ -698,14 +700,19 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
#endif
m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
// SM Orca: 喷嘴直径是挤出机属性,使用 physical_extruder
float nozzle_diameter = float(config.nozzle_diameter.get_at(physical_extruder));
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
if (max_vol_speed!= 0.f)
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
// SM Orca: Store per-filament perimeter width and also set the global one
// Note: m_perimeter_width gets overwritten with each set_extruder() call
// The brim should use m_filpar[0].perimeter_width for consistency
m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio;
m_perimeter_width = m_filpar[idx].perimeter_width; // all extruders are now assumed to have the same diameter
// BBS: remove useless config
#if 0
if (m_semm) {
@@ -1305,7 +1312,10 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
}
// brim chamfer
float spacing = m_perimeter_width - m_layer_height * float(1. - M_PI_4);
// SM Orca: Use first filament's perimeter width for consistent brim spacing
// The brim is generated once for the entire wipe tower and should use a consistent spacing
float brim_perimeter_width = m_filpar.empty() ? m_perimeter_width : m_filpar[0].perimeter_width;
float spacing = brim_perimeter_width - m_layer_height * float(1. - M_PI_4);
// How many perimeters shall the brim have?
int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing;
const float max_chamfer_width = 3.f;
+4 -1
View File
@@ -144,7 +144,8 @@ public:
// Set the extruder properties.
void set_extruder(size_t idx, const PrintConfig& config);
// SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射
void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config);
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
@@ -269,6 +270,8 @@ public:
std::vector<float> ramming_speed;
float nozzle_diameter;
float filament_area;
// SM Orca: Store per-filament perimeter width for correct brim generation
float perimeter_width = 0.f;
};
private:
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -45,7 +45,9 @@ public:
// Set the extruder properties.
void set_extruder(size_t idx, const PrintConfig& config);
// SM Orca: 添加 physical_extruder 参数,用于支持耗材-挤出机映射
// idx: 耗材索引, physical_extruder: 物理挤出机索引
void set_extruder(size_t idx, int physical_extruder, const PrintConfig& config);
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
@@ -160,6 +162,8 @@ public:
float filament_minimal_purge_on_wipe_tower = 0.f;
float retract_length;
float retract_speed;
// SM Orca: Store per-filament perimeter width for correct brim generation
float perimeter_width = 0.f;
};
private:
+18 -10
View File
@@ -27,6 +27,10 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
{
this->config.apply(print_config, true);
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
m_physical_extruder_count = print_config.nozzle_diameter.values.size();
if (m_physical_extruder_count == 0) {
m_physical_extruder_count = 1; // 防止除零,默认为1
}
bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware ||
print_config.gcode_flavor.value == gcfKlipper || print_config.gcode_flavor.value == gcfRepRapFirmware;
m_max_acceleration = std::lrint(use_mach_limits ? print_config.machine_max_acceleration_extruding.values.front() : 0);
@@ -45,17 +49,18 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
{
std::sort(extruder_ids.begin(), extruder_ids.end());
m_extruder = nullptr; // this points to object inside `m_extruders`, so should be cleared too
m_extruders.clear();
m_extruders.reserve(extruder_ids.size());
for (unsigned int extruder_id : extruder_ids)
m_extruders.emplace_back(Extruder(extruder_id, &this->config, config.single_extruder_multi_material.value));
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
for (unsigned int extruder_id : extruder_ids) {
int physical_extruder_id = get_physical_extruder(extruder_id);
m_extruders.emplace_back(Extruder(extruder_id, physical_extruder_id, &this->config, config.single_extruder_multi_material.value));
}
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
}
std::string GCodeWriter::preamble()
@@ -399,7 +404,6 @@ std::string GCodeWriter::set_input_shaping(char axis, float damp, float freq) co
return gcode.str();
}
std::string GCodeWriter::reset_e(bool force)
{
if (FLAVOR_IS(gcfMach3)
@@ -454,6 +458,9 @@ std::string GCodeWriter::toolchange_prefix() const
std::string GCodeWriter::toolchange(unsigned int extruder_id)
{
int physical_extruder = get_physical_extruder(extruder_id);
// set the new extruder
auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
@@ -469,6 +476,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
gcode << " ; change extruder";
gcode << "\n";
gcode << this->reset_e(true);
} else {
}
return gcode.str();
}
@@ -495,7 +503,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
this->set_current_position_clear(true);
//BBS: take plate offset into consider
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
GCodeG1Formatter w;
w.emit_xy(point_on_plate);
auto speed = m_is_first_layer
@@ -713,7 +721,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
m_pos(1) = point(1);
if(std::abs(dE) <= std::numeric_limits<double>::epsilon())
force_no_extrusion = true;
if (!force_no_extrusion)
m_extruder->extrude(dE);
+26
View File
@@ -4,6 +4,7 @@
#include "libslic3r.h"
#include <string>
#include <charconv>
#include <unordered_map>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
@@ -119,6 +120,26 @@ public:
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
// SM Orca: 设置耗材-挤出机映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_filament_extruder_map = map; }
const std::unordered_map<int, int>& get_filament_extruder_map() const { return m_filament_extruder_map; }
// SM Orca: 获取物理挤出机ID
// 关键修复:当映射表为空时,使用模运算而不是直接返回耗材ID,避免越界
// 例如:4个物理挤出机时,耗材0-7分别映射到0,1,2,3,0,1,2,3
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id;
if (it != m_filament_extruder_map.end()) {
// 从映射表获取
physical_extruder_id = it->second;
} else {
// 映射表为空或没有该耗材的映射,使用默认模运算映射
physical_extruder_id = filament_idx % m_physical_extruder_count;
}
return physical_extruder_id;
}
// Returns whether this flavor supports separate print and travel acceleration.
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
private:
@@ -170,6 +191,11 @@ public:
double m_current_speed;
bool m_is_first_layer = true;
// SM Orca: 耗材到物理挤出机的映射表(filament_idx -> physical_extruder_id
std::unordered_map<int, int> m_filament_extruder_map;
// SM Orca: 物理挤出机数量(用于默认模运算映射)
size_t m_physical_extruder_count = 1; // 默认为1,防止除零
enum class Acceleration {
Travel,
Print
+115 -28
View File
@@ -293,8 +293,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_no_sparse_layers"
|| opt_key == "flush_volumes_matrix"
|| opt_key == "prime_volume"
|| opt_key == "prime_tower_brim_chamfer"
|| opt_key == "prime_tower_brim_chamfer_max_width"
|| opt_key == "flush_into_infill"
|| opt_key == "flush_into_support"
|| opt_key == "initial_layer_infill_speed"
@@ -495,6 +493,56 @@ std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
return extruders;
}
// This must be called before the mapping is used (e.g., before export_gcode)
void Print::initialize_filament_extruder_map()
{
m_filament_extruder_map.clear();
// Get the number of physical extruders (number of nozzle_diameter entries)
size_t physical_extruder_count = m_config.nozzle_diameter.values.size();
if (physical_extruder_count == 0) {
BOOST_LOG_TRIVIAL(error) << "Print::initialize_filament_extruder_map: ERROR - No physical extruders configured!";
return;
}
// Get all filament indices that will be used
std::vector<unsigned int> filament_extruders = this->extruders();
// IMPORTANT: Always create mappings for ALL configured filaments, not just those used by objects.
// This is critical because filament override parameters need to access the mapping for all filaments.
// For example, if a user has 8 filaments configured but only uses 4 in their model,
// the mapping table must still contain entries for all 8 filaments to correctly
// inherit parameters from the corresponding physical extruders.
// extruders() returns empty. In this case, use filament_diameter.size() to determine filament count.
// This ensures the mapping is created for all configured filaments, not just those used by objects.
if (filament_extruders.empty()) {
size_t filament_count = m_config.filament_diameter.size();
for (size_t i = 0; i < filament_count; ++i) {
filament_extruders.push_back((unsigned int)i);
}
} else {
// Even if extruders() returns some values, we need to ensure ALL configured filaments are in the map.
// Add any missing filament indices that are configured but not used by objects.
size_t configured_filament_count = m_config.filament_diameter.size();
for (size_t i = 0; i < configured_filament_count; ++i) {
if (std::find(filament_extruders.begin(), filament_extruders.end(), (unsigned int)i) == filament_extruders.end()) {
filament_extruders.push_back((unsigned int)i);
}
}
}
// Create mapping: filament_id -> physical_extruder_id
// Mapping formula: physical_extruder = filament_id % physical_extruder_count
// This allows using 8 filaments with 4 physical extruders:
// filament 0,1,2,3 -> extruder 0,1,2,3
// filament 4,5,6,7 -> extruder 0,1,2,3
for (unsigned int filament_idx : filament_extruders) {
int physical_extruder = filament_idx % physical_extruder_count;
m_filament_extruder_map[filament_idx] = physical_extruder;
}
}
unsigned int Print::num_object_instances() const
{
unsigned int instances = 0;
@@ -506,8 +554,10 @@ unsigned int Print::num_object_instances() const
double Print::max_allowed_layer_height() const
{
double nozzle_diameter_max = 0.;
for (unsigned int extruder_id : this->extruders())
nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id));
for (unsigned int extruder_id : this->extruders()) {
int physical_extruder = get_physical_extruder(extruder_id);
nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(physical_extruder));
}
return nozzle_diameter_max;
}
@@ -1185,10 +1235,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (this->has_wipe_tower() && ! m_objects.empty()) {
// Make sure all extruders use same diameter filament and have the same nozzle diameter
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front());
int first_physical = get_physical_extruder(extruders.front());
double first_nozzle_diam = m_config.nozzle_diameter.get_at(first_physical);
double first_filament_diam = m_config.filament_diameter.get_at(extruders.front());
for (const auto& extruder_idx : extruders) {
double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx);
int physical_extruder = get_physical_extruder(extruder_idx);
double nozzle_diam = m_config.nozzle_diameter.get_at(physical_extruder);
double filament_diam = m_config.filament_diameter.get_at(extruder_idx);
if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam
|| std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) {
@@ -1294,7 +1346,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
double min_nozzle_diameter = std::numeric_limits<double>::max();
double max_nozzle_diameter = 0;
for (unsigned int extruder_id : extruders) {
double dmr = m_config.nozzle_diameter.get_at(extruder_id);
int physical_extruder = get_physical_extruder(extruder_id);
double dmr = m_config.nozzle_diameter.get_at(physical_extruder);
min_nozzle_diameter = std::min(min_nozzle_diameter, dmr);
max_nozzle_diameter = std::max(max_nozzle_diameter, dmr);
}
@@ -1382,9 +1435,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
size_t first_layer_extruder = object->config().raft_layers == 1
? object->config().support_interface_filament-1
: object->config().support_filament-1;
int physical_extruder = get_physical_extruder(first_layer_extruder);
first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
min_nozzle_diameter :
m_config.nozzle_diameter.get_at(first_layer_extruder);
m_config.nozzle_diameter.get_at(physical_extruder);
} else {
// if we don't have raft layers, any nozzle diameter is potentially used in first layer
first_layer_min_nozzle_diameter = min_nozzle_diameter;
@@ -1702,11 +1756,13 @@ Flow Print::brim_flow() const
extruders and take the one with, say, the smallest index.
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
int filament_idx = m_print_regions.front()->config().wall_filament - 1;
int physical_extruder = get_physical_extruder(filament_idx);
return Flow::new_from_config_width(
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1),
(float)m_config.nozzle_diameter.get_at(physical_extruder),
(float)this->skirt_first_layer_height());
}
@@ -1721,11 +1777,13 @@ Flow Print::skirt_flow() const
extruders and take the one with, say, the smallest index;
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
int filament_idx = m_objects.front()->config().support_filament - 1;
int physical_extruder = get_physical_extruder(filament_idx);
return Flow::new_from_config_width(
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_filament-1),
(float)m_config.nozzle_diameter.get_at(physical_extruder),
(float)this->skirt_first_layer_height());
}
@@ -1804,7 +1862,6 @@ void PrintObject::copy_layers_overhang_from_shared_object()
}
}
// BBS
BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std::string& name)
{
@@ -2155,7 +2212,6 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
append(m_first_layer_convex_hull.points, std::move(poly.points));
}
if (has_skirt() && ! draft_shield) {
// In case that draft shield is NOT active, generate skirt now.
// It will be placed around the brim, so brim has to be ready.
@@ -2243,11 +2299,42 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
//BBS: compute plate offset for gcode-generator
const Vec3d origin = this->get_plate_origin();
gcode.set_gcode_offset(origin(0), origin(1));
this->initialize_filament_extruder_map();
for (const auto& pair : m_filament_extruder_map) {
}
gcode.set_filament_extruder_map(m_filament_extruder_map);
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
//BBS
result->conflict_result = m_conflict_result;
return path.c_str();
// After G-code export is complete, finalize the output path by replacing placeholders with actual values
// This ensures that placeholders like {print_time} are replaced with calculated values
// Note: This is needed for direct export (not through BackgroundSlicingProcess) where
// finalize_output_path() might not be called automatically
std::string final_path = this->print_statistics().finalize_output_path(path);
// Rename the file from the placeholder path to the finalized path
if (final_path != path) {
std::error_code ret = rename_file(path, final_path);
if (ret) {
BOOST_LOG_TRIVIAL(warning) << "Failed to rename G-code file from '" << path
<< "' to '" << final_path << "': " << ret.message();
// If rename fails, return the original path
return path;
} else {
BOOST_LOG_TRIVIAL(info) << "Renamed G-code file from '" << path
<< "' to '" << final_path << "'";
// Update result filename to reflect the new path
if (result) {
result->filename = final_path;
}
}
}
return final_path;
}
void Print::_make_skirt()
@@ -2333,7 +2420,8 @@ void Print::_make_skirt()
extruders_e_per_mm.reserve(set_extruders.size());
for (auto &extruder_id : set_extruders) {
extruders.push_back(extruder_id);
extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm));
int physical_extruder_id = get_physical_extruder(extruder_id);
extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, physical_extruder_id, &m_config, m_config.single_extruder_multi_material).e_per_mm(mm3_per_mm));
}
}
@@ -2733,8 +2821,10 @@ void Print::_make_wipe_tower()
// wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < number_of_extruders; ++i)
wipe_tower.set_extruder(i, m_config);
for (size_t i = 0; i < number_of_extruders; ++i) {
int physical_extruder = get_physical_extruder(i);
wipe_tower.set_extruder(i, physical_extruder, m_config);
}
// BBS: remove priming logic
// m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>(
@@ -2829,8 +2919,10 @@ void Print::_make_wipe_tower()
// wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < number_of_extruders; ++i)
wipe_tower.set_extruder(i, m_config);
for (size_t i = 0; i < number_of_extruders; ++i) {
int physical_extruder = get_physical_extruder(i);
wipe_tower.set_extruder(i, physical_extruder, m_config);
}
m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>(
wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
@@ -2920,7 +3012,10 @@ std::string Print::output_filename(const std::string &filename_base) const
{
// Set the placeholders for the data know first after the G-code export is finished.
// These values will be just propagated into the output file name.
DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
// Use cached statistics if available (even if not finished) to avoid placeholders like {print_time}
const PrintStatistics& stats = this->print_statistics();
bool has_valid_stats = stats.total_used_filament > 0 || !stats.estimated_normal_print_time.empty();
DynamicConfig config = (this->finished() || has_valid_stats) ? stats.config() : stats.placeholders();
config.set_key_value("num_filaments", new ConfigOptionInt((int)m_config.nozzle_diameter.size()));
config.set_key_value("num_extruders", new ConfigOptionInt((int) m_config.nozzle_diameter.size()));
config.set_key_value("plate_name", new ConfigOptionString(get_plate_name()));
@@ -2970,6 +3065,7 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
const Vec3d origin = this->get_plate_origin();
processor.set_xy_offset(origin(0), origin(1));
processor.set_filament_extruder_map(m_filament_extruder_map);
//processor.enable_producers(true);
processor.process_file(file);
@@ -3113,7 +3209,6 @@ const std::string PrintStatistics::TotalFilamentCostValueMask = "; total filamen
const std::string PrintStatistics::TotalFilamentUsedWipeTower = "total filament used for wipe tower [g]";
const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; total filament used for wipe tower [g] = %.2lf\n";
/*add json export/import related functions */
#define JSON_POLYGON_CONTOUR "contour"
#define JSON_POLYGON_HOLES "holes"
@@ -3123,7 +3218,6 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota
#define JSON_OBJECT_NAME "name"
#define JSON_IDENTIFY_ID "identify_id"
#define JSON_LAYERS "layers"
#define JSON_SUPPORT_LAYERS "support_layers"
#define JSON_TREE_SUPPORT_LAYERS "tree_support_layers"
@@ -3160,8 +3254,6 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota
#define JSON_LAYER_REGION_PERIMETERS "perimeters"
#define JSON_LAYER_REGION_FILLS "fills"
#define JSON_SURF_TYPE "surface_type"
#define JSON_SURF_THICKNESS "thickness"
#define JSON_SURF_THICKNESS_LAYER "thickness_layers"
@@ -3201,7 +3293,6 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota
#define JSON_EXTRUSION_NO_EXTRUSION "no_extrusion"
#define JSON_EXTRUSION_LOOP_ROLE "loop_role"
static void to_json(json& j, const Points& p_s) {
for (const Point& p : p_s)
{
@@ -3265,7 +3356,6 @@ static void to_json(json& j, const ArcSegment& arc_seg) {
j[JSON_ARC_CENTER] = std::move(center_point_json);
}
static void to_json(json& j, const Polyline& poly_line) {
json points_json = json::array(), fittings_json = json::array();
points_json = poly_line.points;
@@ -3539,7 +3629,6 @@ static void from_json(const json& j, ArcSegment& arc_seg) {
return;
}
static void from_json(const json& j, Polyline& poly_line) {
poly_line.points = j[JSON_POINTS];
@@ -3750,7 +3839,6 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg
return;
}
void extract_layer(const json& layer_json, Layer& layer) {
//slice_polygons
int slice_polygons_count = layer_json[JSON_LAYER_SLICED_POLYGONS].size();
@@ -4121,7 +4209,6 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
return ret;
}
int Print::load_cached_data(const std::string& directory)
{
int ret = 0;
+32
View File
@@ -23,6 +23,7 @@
#include <functional>
#include <set>
#include <unordered_map>
#include "calib.hpp"
@@ -887,6 +888,34 @@ public:
std::vector<unsigned int> object_extruders() const;
std::vector<unsigned int> support_material_extruders() const;
// SM Orca: 设置耗材-挤出机映射
void set_filament_extruder_map(const std::unordered_map<int, int>& map) { m_filament_extruder_map = map; }
// SM Orca: 获取耗材-挤出机映射表
const std::unordered_map<int, int>& get_filament_extruder_map() const { return m_filament_extruder_map; }
// SM Orca: 获取物理挤出机ID(根据耗材索引)
// 关键修复:当映射表为空时,使用模运算而不是直接返回耗材ID,避免越界
int get_physical_extruder(int filament_idx) const {
auto it = m_filament_extruder_map.find(filament_idx);
int physical_extruder_id;
if (it != m_filament_extruder_map.end()) {
// 从映射表获取
physical_extruder_id = it->second;
} else {
// 映射表为空或没有该耗材的映射,使用默认模运算映射
size_t physical_count = m_config.nozzle_diameter.values.size();
if (physical_count == 0) {
// 防止除零,使用安全的默认值
physical_extruder_id = 0;
} else {
physical_extruder_id = filament_idx % physical_count;
}
}
return physical_extruder_id;
}
// SM Orca: Initialize filament-to-physical-extruder mapping table
void initialize_filament_extruder_map();
std::vector<unsigned int> extruders(bool conside_custom_gcode = false) const;
double max_allowed_layer_height() const;
bool has_support_material() const;
@@ -1066,6 +1095,9 @@ private:
//SoftFever: calibration
Calib_Params m_calib_params;
// SM Orca: 耗材到物理挤出机的映射表
std::unordered_map<int, int> m_filament_extruder_map;
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
// Allow PrintObject to access m_mutex and m_cancel_callback.
+215 -6
View File
@@ -3,6 +3,7 @@
#include <boost/log/trivial.hpp>
#include <cfloat>
#include <sstream>
namespace Slic3r {
@@ -216,13 +217,133 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<Custo
return false;
}
// For each filament slot, if no override is provided, inherit from the mapped physical extruder
// IMPORTANT: This function creates a NEW target array with filament_count elements
static ConfigOption* apply_physical_extruder_defaults(
const ConfigOption* filament_overrides,
const ConfigOption* extruder_defaults,
size_t filament_count,
const std::unordered_map<int, int>& filament_extruder_map)
{
if (!extruder_defaults->is_vector())
return nullptr;
auto* extruder_vec = dynamic_cast<const ConfigOptionVectorBase*>(extruder_defaults);
const ConfigOptionVectorBase* override_vec = filament_overrides ?
dynamic_cast<const ConfigOptionVectorBase*>(filament_overrides) : nullptr;
if (!extruder_vec)
return nullptr;
// Clone the extruder defaults to create the target
auto* target = extruder_defaults->clone();
auto* target_vec = dynamic_cast<ConfigOptionVectorBase*>(target);
if (!target_vec) {
delete target;
return nullptr;
}
// Resize target to filament_count
target_vec->resize(filament_count);
for (size_t filament_idx = 0; filament_idx < filament_count; ++filament_idx) {
bool has_override = false;
if (override_vec && filament_idx < override_vec->size()) {
if (override_vec->nullable()) {
// Nullable type: use override only if not nil (checkbox is checked)
has_override = !override_vec->is_nil(filament_idx);
} else {
// Non-nullable type: check if the value differs from the default (printer config)
// Only if it's different do we consider it a user override
// Get the default value from the mapped physical extruder
auto map_it = filament_extruder_map.find(filament_idx);
int physical_extruder_idx;
if (map_it != filament_extruder_map.end()) {
physical_extruder_idx = map_it->second;
} else {
// Fallback: use modulo to map filament to physical extruder
// This handles edge cases where the map is incomplete or filament_idx is out of range
size_t physical_extruder_count = extruder_vec->size();
if (physical_extruder_count == 0) {
// Should not happen, but safety check
physical_extruder_idx = 0;
} else {
physical_extruder_idx = (int)filament_idx % (int)physical_extruder_count;
}
}
if (physical_extruder_idx < extruder_vec->size()) {
// Try different types: double, int, bool
auto* override_dbl = dynamic_cast<const ConfigOptionVector<double>*>(override_vec);
auto* extruder_dbl = dynamic_cast<const ConfigOptionVector<double>*>(extruder_vec);
if (override_dbl && extruder_dbl) {
// Compare with the value from the mapped physical extruder
double override_value = override_dbl->get_at(filament_idx);
double default_value = extruder_dbl->get_at(physical_extruder_idx);
// Use override only if value differs from default
has_override = (override_value != default_value);
} else {
// Try int type
auto* override_int = dynamic_cast<const ConfigOptionVector<int>*>(override_vec);
auto* extruder_int = dynamic_cast<const ConfigOptionVector<int>*>(extruder_vec);
if (override_int && extruder_int) {
int override_value = override_int->get_at(filament_idx);
int default_value = extruder_int->get_at(physical_extruder_idx);
has_override = (override_value != default_value);
} else {
// Try bool type
auto* override_bool = dynamic_cast<const ConfigOptionVector<unsigned char>*>(override_vec);
auto* extruder_bool = dynamic_cast<const ConfigOptionVector<unsigned char>*>(extruder_vec);
if (override_bool && extruder_bool) {
unsigned char override_value = override_bool->get_at(filament_idx);
unsigned char default_value = extruder_bool->get_at(physical_extruder_idx);
has_override = (override_value != default_value);
}
}
}
}
}
}
if (!has_override) {
// No override: inherit from the mapped physical extruder
auto map_it = filament_extruder_map.find(filament_idx);
int physical_extruder_idx;
if (map_it != filament_extruder_map.end()) {
physical_extruder_idx = map_it->second;
} else {
// Fallback: use modulo to map filament to physical extruder
// This handles edge cases where the map is incomplete or filament_idx is out of range
size_t physical_extruder_count = extruder_vec->size();
if (physical_extruder_count == 0) {
// Should not happen, but safety check
physical_extruder_idx = 0;
} else {
physical_extruder_idx = (int)filament_idx % (int)physical_extruder_count;
}
}
if (physical_extruder_idx < extruder_vec->size() && filament_idx < target_vec->size()) {
target_vec->set_at(extruder_vec, filament_idx, physical_extruder_idx);
}
} else if (override_vec && filament_idx < override_vec->size()) {
// Has override: use the value from filament config
target_vec->set_at(override_vec, filament_idx, filament_idx);
}
}
return target;
}
// Collect changes to print config, account for overrides of extruder retract values by filament presets.
//BBS: add plate index
static t_config_option_keys print_config_diffs(
const PrintConfig &current_config,
const DynamicPrintConfig &new_full_config,
DynamicPrintConfig &filament_overrides,
int plate_index)
int plate_index,
const std::unordered_map<int, int> &filament_extruder_map)
{
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
const std::string filament_prefix = "filament_";
@@ -241,25 +362,81 @@ static t_config_option_keys print_config_diffs(
// const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
const ConfigOption* opt_new_filament = (iter == extruder_retract_keys.end()) ? nullptr :
new_full_config.option(filament_prefix + opt_key);
if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
bool is_extruder_retract_param = (iter != extruder_retract_keys.end());
// 1. This is an extruder retract parameter AND
// 2. Filament overrides exist AND
// 3. Filament-extruder map is not empty (meaning objects are loaded and mapping is initialized)
// When user edits printer config directly (without objects loaded or without filament overrides),
// we should treat it as a regular config change to ensure UI updates work correctly.
bool has_filament_overrides = (opt_new_filament != nullptr && !opt_new_filament->is_nil());
bool needs_physical_mapping = is_extruder_retract_param && has_filament_overrides && !filament_extruder_map.empty();
if (needs_physical_mapping) {
// This is safe because both opt_old and opt_new should have the same number of physical extruders
bool printer_config_changed = (*opt_old != *opt_new);
auto* override_vec = dynamic_cast<const ConfigOptionVectorBase*>(opt_new_filament);
if (override_vec) {
BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key
<< " - filament_override size=" << override_vec->size()
<< ", nullable=" << override_vec->nullable();
}
// since we know has_filament_overrides is true at this point
if ((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut")
&& new_full_config.option<ConfigOptionInt>("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)
continue;
// - Check if printer config or filament override changed the effective value
// - Only add to print_diff (not filament_overrides) to avoid array size mismatch
// The actual filament->extruder mapping is applied later during config usage
//
// 关键修复:只要打印机配置变化了,就应该添加到 print_diff
// 这确保了用户在UI中修改打印机配置时,修改能被正确保存
auto opt_copy = opt_new->clone();
opt_copy->apply_override(opt_new_filament);
if (printer_config_changed || *opt_old != *opt_copy) {
print_diff.emplace_back(opt_key);
BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key
<< " - adding to print_diff (printer_changed=" << (printer_config_changed ? "Y" : "N")
<< ", effective_changed=" << (*opt_old != *opt_copy ? "Y" : "N") << ")";
}
delete opt_copy;
} else if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
// An extruder retract override is available at some of the filament presets.
bool overriden = opt_new->overriden_by(opt_new_filament);
if (overriden || *opt_old != *opt_new) {
bool printer_config_changed = (*opt_old != *opt_new);
if (overriden || printer_config_changed) {
auto opt_copy = opt_new->clone();
if (!((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut")
&& new_full_config.option<ConfigOptionInt>("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)) // ugly code, remove it later if firmware supports
opt_copy->apply_override(opt_new_filament);
bool changed = *opt_old != *opt_copy;
if (changed)
print_diff.emplace_back(opt_key);
if (changed || overriden) {
// If user directly edited printer config, don't override it with filament values
if ((changed || overriden) && !printer_config_changed) {
if ((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut")
&& new_full_config.option<ConfigOptionInt>("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)
continue;
// filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config.
filament_overrides.set_key_value(opt_key, opt_copy);
} else
} else if (changed && printer_config_changed) {
// Only add to print_diff, not to filament_overrides
// This preserves user's printer config edit
BOOST_LOG_TRIVIAL(info) << "print_config_diffs: " << opt_key
<< " - printer config changed, not adding to filament_overrides to preserve user edit";
delete opt_copy;
} else {
delete opt_copy;
}
}
} else if (*opt_new != *opt_old) {
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
@@ -1094,6 +1271,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// BBS
int used_filaments = this->extruders(true).size();
// 此时 m_objects 和 m_config 是稳定的,可以安全地计算映射
// 这确保了 print_config_diffs 中的参数继承机制能正常工作
this->initialize_filament_extruder_map();
//new_full_config.normalize_fdm(used_filaments);
new_full_config.normalize_fdm_1();
t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments);
@@ -1131,12 +1312,36 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
DynamicPrintConfig filament_overrides;
//BBS: add plate index
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index);
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, m_filament_extruder_map);
t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index);
// Collect changes to object and region configs.
t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);
t_config_option_keys region_diff = m_default_region_config.diff(new_full_config);
//
// 问题根源:
// 1. 回抽参数(如retraction_length)既存在于打印机配置,也可能被耗材覆盖
// 2. 当耗材-挤出机映射激活时(m_filament_extruder_map不为空),
// filament_overrides中的回抽值会覆盖用户对打印机配置的直接修改
//
// 修复策略:
// - 当存在耗材-挤出机映射时(说明已加载项目),移除filament_overrides中的所有回抽参数
// - 这样用户的打印机配置修改就不会被耗材覆盖值覆盖
// - 保留filament_overrides中其他参数的功能不受影响
//
// 注意:此修复确保用户对打印机挤出机回抽参数的直接编辑拥有最高优先级
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
bool has_mapping = !m_filament_extruder_map.empty();
if (has_mapping) {
// 当有映射时,移除所有回抽参数的耗材覆盖,让打印机配置生效
for (const std::string &key : extruder_retract_keys) {
if (filament_overrides.erase(key)) {
BOOST_LOG_TRIVIAL(info) << "Print::apply - Clearing filament override for '" << key
<< "' to allow printer config to take effect (filament-extruder mapping active)";
}
}
}
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
auto update_apply_status = [&apply_status](bool invalidated)
@@ -1550,6 +1755,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_full_print_config = std::move(new_full_config);
}
// 在对象同步后,m_objects 可能已经被重建,需要重新计算映射以反映最新状态
// 这确保了后续使用映射表时(如 export_gcode)能获得正确的映射关系
this->initialize_filament_extruder_map();
// All regions now have distinct settings.
// Check whether applying the new region config defaults we would get different regions,
// update regions or create regions from scratch.
+4 -2
View File
@@ -504,8 +504,10 @@ set(SLIC3R_GUI_SOURCES
GUI/SMPhysicalPrinterDialog.cpp
GUI/SSWCP.cpp
GUI/SSWCP.hpp
GUI/WCPDownloadManager.cpp
GUI/WCPDownloadManager.hpp
GUI/DownloadManager.cpp
GUI/DownloadManager.hpp
GUI/GenericDownloadDialog.cpp
GUI/GenericDownloadDialog.hpp
GUI/WebPresetDialog.hpp
GUI/WebPresetDialog.cpp
GUI/WebSMUserLoginDialog.cpp
+1
View File
@@ -57,6 +57,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id)
m_cancelbutton->SetBorderColor(btn_bd_white);
m_cancelbutton->SetTextColor(btn_txt_white);
m_cancelbutton->SetCornerRadius(m_self->FromDIP(12));
m_cancelbutton->SetCursor(wxCURSOR_HAND);
m_cancelbutton->Bind(wxEVT_BUTTON,
[this](wxCommandEvent &evt) {
m_was_cancelled = true;
+579
View File
@@ -0,0 +1,579 @@
#include "DownloadManager.hpp"
#include "GUI_App.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <vector>
#include <ctime>
namespace Slic3r { namespace GUI {
// ============================================================================
// Helper Functions
// ============================================================================
std::string DownloadManager::get_unique_file_path(const boost::filesystem::path& file_path)
{
// file_path should be the complete absolute path: directory + filename
std::string original_path = file_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Checking path '%1%'") % original_path;
// Check if file exists, if not return original path
if (!boost::filesystem::exists(file_path)) {
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File does not exist, returning original path '%1%'") % original_path;
return original_path;
}
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File exists, generating unique name");
boost::filesystem::path parent_dir = file_path.parent_path();
std::string filename = file_path.filename().string();
std::string extension = file_path.extension().string();
std::string name_without_ext;
if (extension.empty()) {
name_without_ext = filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: No extension found, filename='%1%'") % filename;
} else {
name_without_ext = filename.substr(0, filename.size() - extension.size());
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: filename='%1%', extension='%2%', name_without_ext='%3%'")
% filename % extension % name_without_ext;
}
// Generate unique filename with Windows-style numbering: filename(1).ext, filename(2).ext, etc.
size_t version = 1;
boost::filesystem::path unique_path;
do {
std::string new_filename;
if (extension.empty()) {
// No extension: filename(1), filename(2), etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")";
} else {
// Has extension: filename(1).ext, filename(2).ext, etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")" + extension;
}
unique_path = parent_dir / new_filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Trying version %1%: '%2%'") % version % unique_path.string();
version++;
} while (boost::filesystem::exists(unique_path) && version < 10000); // Safety limit
if (version >= 10000) {
// If we hit the limit, log a warning and return a timestamp-based name
BOOST_LOG_TRIVIAL(warning) << boost::format("DownloadManager::get_unique_file_path: Too many duplicate files for '%1%', using timestamp-based name")
% original_path;
std::string timestamp = std::to_string(std::time(nullptr));
std::string new_filename;
if (extension.empty()) {
new_filename = name_without_ext + "_" + timestamp;
} else {
new_filename = name_without_ext + "_" + timestamp + extension;
}
unique_path = parent_dir / new_filename;
}
std::string result = unique_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Final unique path: '%1%'") % result;
return result;
}
// ============================================================================
// WCP Download Interface (for Web-to-PC communication)
// ============================================================================
size_t DownloadManager::start_wcp_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance,
bool use_original_event_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
// Update file_name if it was changed due to duplicate
std::string actual_file_name = boost::filesystem::path(dest_path).filename().string();
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
actual_file_name,
dest_path,
wcp_instance,
use_original_event_id);
task->state = DownloadTaskState::Downloading;
m_tasks[task_id] = task;
start_download_impl(task);
return task_id;
}
// ============================================================================
// Internal Download Interface (for PC internal use)
// ============================================================================
size_t DownloadManager::start_internal_download(const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
DownloadCallbacks callbacks) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
boost::filesystem::path dest_path_obj(dest_path);
boost::filesystem::path dest_file_path;
if (boost::filesystem::is_directory(dest_path_obj) || dest_path_obj.filename().empty()) {
// dest_path is a directory, need to append file_name
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a directory, appending file_name '%2%'")
% dest_path % file_name;
dest_file_path = dest_path_obj / file_name;
} else {
// dest_path is already a complete file path (directory + filename)
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a complete file path")
% dest_path;
dest_file_path = dest_path_obj;
}
boost::filesystem::create_directories(dest_file_path.parent_path());
std::string unique_dest_path = get_unique_file_path(dest_file_path);
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
file_name,
unique_dest_path,
std::move(callbacks));
task->state = DownloadTaskState::Downloading;
m_tasks[task_id] = task;
start_download_impl(task);
return task_id;
}
size_t DownloadManager::start_internal_download(const std::string& file_url,
const std::string& file_name,
DownloadCallbacks callbacks) {
// Get default download path
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
return start_internal_download(file_url, file_name, dest_path, std::move(callbacks));
}
void DownloadManager::start_download_impl(std::shared_ptr<DownloadTask> task) {
wxGetApp().CallAfter([this, task]() {
try {
// Step 1: Create Http object
Http http = Http::get(task->file_url);
http.timeout_max(0);
// Step 2: Set progress callback
http.on_progress([this, task](Http::Progress progress, bool& cancel) {
// Check if task is canceled or already cleaned up
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) == m_tasks.end()) {
// Task has been cleaned up, cancel the download
cancel = true;
return;
}
}
if (task->state == DownloadTaskState::Canceled) {
cancel = true;
return;
}
int percent = 0;
if (progress.dltotal > 0) {
percent = (int)(progress.dlnow * 100 / progress.dltotal);
}
task->percent = percent;
// Throttle progress updates: update every 5% or every second
std::lock_guard<std::mutex> lock(m_tasks_mutex);
// Double-check task still exists after acquiring lock
if (m_tasks.find(task->task_id) == m_tasks.end()) {
cancel = true;
return;
}
auto& last_pct = m_last_percent[task->task_id];
auto& last_upd = m_last_update[task->task_id];
auto now = std::chrono::steady_clock::now();
bool should_update = false;
if (percent - last_pct >= 5) {
should_update = true;
last_pct = percent;
} else if (now - last_upd >= std::chrono::seconds(1)) {
should_update = true;
}
if (should_update) {
last_upd = now;
wxGetApp().CallAfter([this, task, percent, progress]() {
// Check if task still exists before sending update
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) != m_tasks.end() &&
task->state != DownloadTaskState::Canceled) {
send_progress_update(task, percent, progress.dlnow, progress.dltotal);
}
});
}
});
// Step 3: Set complete callback
http.on_complete([this, task](std::string body, unsigned status) {
wxGetApp().CallAfter([this, task, body]() {
// Check if task still exists and is not canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task has been canceled, ignore completion
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring complete callback for canceled task " << task->task_id;
return;
}
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
std::string error_msg = "Failed to open file for writing: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg;
// Flush logs immediately for critical errors
Slic3r::flush_logs();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
if (file.fail()) {
std::string error_msg = "Failed to write file: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", body size: " << body.size();
// Flush logs immediately for critical errors
Slic3r::flush_logs();
file.close();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.close();
task->state = DownloadTaskState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
std::string error_msg = std::string("File write exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", file: " << task->dest_path;
// Flush logs immediately for critical errors
Slic3r::flush_logs();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
}
});
});
// Step 4: Set error callback
http.on_error([this, task](std::string body, std::string error, unsigned status) {
wxGetApp().CallAfter([this, task, error, status]() {
// Check if task was canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task was canceled, ignore error callback (cancel already handled cleanup)
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring error callback for canceled task " << task->task_id;
return;
}
std::string error_msg = boost::str(boost::format("HTTP error: %1% (status: %2%)") % error % status);
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
// Flush logs immediately for critical errors to ensure they are written
Slic3r::flush_logs();
task->state = DownloadTaskState::Error;
task->error_message = error;
send_error_update(task, error);
cleanup_task(task->task_id);
});
});
// Step 5: Start download and save Http::Ptr for cancellation
task->http_object = http.perform();
} catch (std::exception& e) {
std::string error_msg = std::string("Download exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
task->state = DownloadTaskState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
}
//this function not currently in use
bool DownloadManager::cancel_download(size_t task_id) {
std::shared_ptr<SSWCP_Instance> wcp_to_destroy;
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it == m_tasks.end()) {
return false;
}
auto task = it->second;
if (task->state == DownloadTaskState::Downloading) {
task->state = DownloadTaskState::Canceled;
if (task->http_object) {
task->http_object->cancel();
}
// Only for WCP downloads
if (task->is_wcp_download()) {
wcp_to_destroy = task->wcp_instance.lock();
} else {
task->callbacks.on_error = nullptr;
task->callbacks.on_progress = nullptr;
task->callbacks.on_complete = nullptr;
}
// Cleanup task directly (already holding the lock, don't call cleanup_task)
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
} else {
return false;
}
}
if (wcp_to_destroy) {
wcp_to_destroy->finish_job();
}
return true;
}
// this function not currently in use
bool DownloadManager::pause_download(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == DownloadTaskState::Downloading) {
it->second->state = DownloadTaskState::Paused;
return true;
}
return false;
}
// this function not currently in use
bool DownloadManager::resume_download(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == DownloadTaskState::Paused) {
return false;
}
return false;
}
// this function not currently in use
DownloadTaskState DownloadManager::get_task_state(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second->state;
}
return DownloadTaskState::Error;
}
// this function not currently in use
std::shared_ptr<DownloadTask> DownloadManager::get_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second;
}
return nullptr;
}
// this function not currently in use
std::vector<std::shared_ptr<DownloadTask>> DownloadManager::get_all_tasks() {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
std::vector<std::shared_ptr<DownloadTask>> result;
result.reserve(m_tasks.size());
for (const auto& pair : m_tasks) {
result.push_back(pair.second);
}
return result;
}
// ============================================================================
// Progress/Complete/Error Update Handlers
// ============================================================================
void DownloadManager::send_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (task->is_wcp_download()) {
send_wcp_progress_update(task, percent, downloaded, total);
} else {
call_internal_progress_callback(task, percent, downloaded, total);
}
}
void DownloadManager::send_wcp_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json progress_data;
progress_data["task_id"] = task->task_id;
progress_data["percent"] = percent;
progress_data["downloaded"] = downloaded;
progress_data["total"] = total;
progress_data["state"] = "downloading";
wcp->m_res_data = progress_data;
wcp->m_status = 0;
wcp->m_msg = "Download progress";
json header;
if (task->use_original_event_id) {
header["event_id"] = wcp->m_event_id;
} else {
header["event_id"] = wcp->m_event_id + "_progress";
}
header["command"] = "download_progress";
wcp->m_header = header;
wcp->send_to_js();
}
}
void DownloadManager::call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (task->callbacks.on_progress && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_progress(task->task_id, percent, downloaded, total);
}
}
void DownloadManager::send_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (task->is_wcp_download()) {
send_wcp_complete_update(task, file_path);
} else {
call_internal_complete_callback(task, file_path);
}
}
void DownloadManager::send_wcp_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json complete_data;
complete_data["task_id"] = task->task_id;
complete_data["file_path"] = file_path;
complete_data["file_name"] = task->file_name;
complete_data["percent"] = 100;
complete_data["state"] = "completed";
wcp->m_res_data = complete_data;
wcp->m_status = 0;
wcp->m_msg = "Download completed";
wcp->send_to_js();
wcp->finish_job();
}
}
void DownloadManager::call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (task->callbacks.on_complete && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_complete(task->task_id, file_path);
}
}
void DownloadManager::send_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (task->is_wcp_download()) {
send_wcp_error_update(task, error);
} else {
call_internal_error_callback(task, error);
}
}
void DownloadManager::send_wcp_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json error_data;
error_data["task_id"] = task->task_id;
error_data["error"] = error;
error_data["state"] = "error";
wcp->m_res_data = error_data;
wcp->m_status = -1;
wcp->m_msg = error;
wcp->send_to_js();
wcp->finish_job();
}
}
void DownloadManager::call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (task->callbacks.on_error && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_error(task->task_id, error);
}
}
void DownloadManager::cleanup_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
}
}} // namespace Slic3r::GUI
+210
View File
@@ -0,0 +1,210 @@
#ifndef slic3r_DownloadManager_hpp_
#define slic3r_DownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include <functional>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state (renamed to avoid conflict with Downloader::DownloadState)
enum class DownloadTaskState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download callback interface for internal downloads
struct DownloadCallbacks {
std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)> on_progress;
std::function<void(size_t task_id, const std::string& file_path)> on_complete;
std::function<void(size_t task_id, const std::string& error)> on_error;
DownloadCallbacks() = default;
DownloadCallbacks(
std::function<void(size_t, int, size_t, size_t)> progress,
std::function<void(size_t, const std::string&)> complete,
std::function<void(size_t, const std::string&)> error)
: on_progress(std::move(progress))
, on_complete(std::move(complete))
, on_error(std::move(error))
{}
};
// Download task information
struct DownloadTask {
size_t task_id;
std::string file_url;
std::string file_name;
std::string dest_path;
std::weak_ptr<SSWCP_Instance> wcp_instance;
DownloadCallbacks callbacks;
Http::Ptr http_object;
DownloadTaskState state;
int percent;
std::string error_message;
bool auto_finish_job;
bool use_original_event_id;
// Constructor for WCP downloads
DownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, std::shared_ptr<SSWCP_Instance> instance,
bool use_original_event = false)
: task_id(id), file_url(url), file_name(name), dest_path(path)
, wcp_instance(instance), state(DownloadTaskState::Pending), percent(0)
, auto_finish_job(false), use_original_event_id(use_original_event)
{}
// Constructor for internal downloads
DownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, DownloadCallbacks cb)
: task_id(id), file_url(url), file_name(name), dest_path(path)
, callbacks(std::move(cb)), state(DownloadTaskState::Pending), percent(0)
, auto_finish_job(false), use_original_event_id(false)
{}
// Check if this is a WCP download
bool is_wcp_download() const {
return !wcp_instance.expired();
}
};
class DownloadManager {
public:
static DownloadManager& getInstance() {
static DownloadManager instance;
return instance;
}
// ============================================================================
// WCP Download Interface (for Web-to-PC communication)
// ============================================================================
size_t start_wcp_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance,
bool use_original_event_id = false);
// ============================================================================
// Internal Download Interface (for PC internal use)
// ============================================================================
size_t start_internal_download(const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
DownloadCallbacks callbacks);
size_t start_internal_download(const std::string& file_url,
const std::string& file_name,
DownloadCallbacks callbacks);
// ============================================================================
// Common Interface (works for both WCP and internal downloads)
// ============================================================================
// Cancel a download task
bool cancel_download(size_t task_id);
// Pause a download task (if needed)
bool pause_download(size_t task_id);
// Resume a download task (if needed)
bool resume_download(size_t task_id);
// Get task state
DownloadTaskState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<DownloadTask> get_task(size_t task_id);
// Get all active tasks
std::vector<std::shared_ptr<DownloadTask>> get_all_tasks();
private:
DownloadManager() = default;
~DownloadManager() = default;
DownloadManager(const DownloadManager&) = delete;
DownloadManager& operator=(const DownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<DownloadTask>> m_tasks;
std::atomic<size_t> m_next_task_id{1};
// Track last progress update for throttling
std::unordered_map<size_t, int> m_last_percent;
std::unordered_map<size_t, std::chrono::steady_clock::time_point> m_last_update;
// ============================================================================
// Internal Implementation
// ============================================================================
// Common download implementation (used by both WCP and internal downloads)
void start_download_impl(std::shared_ptr<DownloadTask> task);
// Send progress update (handles both WCP and internal modes)
void send_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// Send completion message (handles both WCP and internal modes)
void send_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// Send error message (handles both WCP and internal modes)
void send_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// WCP-specific: Send progress update via WCP instance
void send_wcp_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// WCP-specific: Send completion via WCP instance
void send_wcp_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// WCP-specific: Send error via WCP instance
void send_wcp_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Internal-specific: Call progress callback
void call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// Internal-specific: Call complete callback
void call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// Internal-specific: Call error callback
void call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
// Generate unique file path if file already exists
// Returns path like "file(1).zip", "file(2).zip" etc.
static std::string get_unique_file_path(const boost::filesystem::path& file_path);
};
}} // namespace Slic3r::GUI
#endif // slic3r_DownloadManager_hpp_
+2 -2
View File
@@ -136,12 +136,12 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
}
case coPercents:{
ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix - use src_idx=0 for single-element vectors
break;
}
case coFloats:{
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, 0); // SM Orca: Fix - use src_idx=0 for single-element vectors
break;
}
case coString:
+9 -16
View File
@@ -13,7 +13,7 @@
#include "slic3r/GUI/WebPresetDialog.hpp"
#include "slic3r/GUI/SSWCP.hpp"
#include "slic3r/GUI/WCPDownloadManager.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/Config/Version.hpp"
@@ -1066,7 +1066,7 @@ GUI_App::GUI_App()
, m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>())
, m_wcp_download_manager(&WCPDownloadManager::getInstance())
, m_download_manager(&DownloadManager::getInstance())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in Snapmaker_Orca.cpp
@@ -1082,7 +1082,9 @@ GUI_App::GUI_App()
m_page_http_server.setPort(PAGE_HTTP_PORT);
m_page_http_server.set_request_handler(HttpServer::web_server_handle_request);
m_page_http_server.start();
BOOST_LOG_TRIVIAL(info) << "[Flutter] Version:"<<common::get_flutter_version();
BOOST_LOG_TRIVIAL(info) << "[Profile] Version:" << common::get_profile_version();
flush_logs();
m_fltviews.set_app(this);
}
@@ -4895,7 +4897,7 @@ void GUI_App::check_new_version_sf(bool show_tips, bool by_user)
BOOST_LOG_TRIVIAL(fatal) << "request server soft update data error:" << errorMsg;
}
})
.perform_sync();
.perform();
}
void GUI_App::process_network_msg(std::string dev_id, std::string msg)
{
@@ -6482,9 +6484,9 @@ Downloader* GUI_App::downloader()
return m_downloader.get();
}
WCPDownloadManager* GUI_App::wcp_download_manager()
DownloadManager* GUI_App::download_manager()
{
return m_wcp_download_manager;
return m_download_manager;
}
void GUI_App::load_url(wxString url)
@@ -6979,16 +6981,7 @@ bool GUI_App::config_wizard_startup()
BOOST_LOG_TRIVIAL(info) << "finished run wizard";
return true;
} /*else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
run_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}*/
}
if (isAgree.empty())
{
+3 -3
View File
@@ -89,7 +89,7 @@ class Plater;
class ParamsPanel;
class NotificationManager;
class Downloader;
class WCPDownloadManager;
class DownloadManager;
struct GUI_InitParams;
class ParamsDialog;
class HMSQuery;
@@ -298,7 +298,7 @@ private:
size_t m_instance_hash_int;
std::unique_ptr<Downloader> m_downloader;
WCPDownloadManager* m_wcp_download_manager;
DownloadManager* m_download_manager;
//BBS
bool m_is_closing {false};
@@ -686,7 +686,7 @@ private:
Model& model();
NotificationManager * notification_manager();
Downloader* downloader();
WCPDownloadManager* wcp_download_manager();
DownloadManager* download_manager();
std::string m_mall_model_download_url;
+434
View File
@@ -0,0 +1,434 @@
#include "GenericDownloadDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/textctrl.h>
#include <wx/scrolwin.h>
#include <wx/event.h>
#include <wx/dcgraph.h>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
namespace Slic3r {
namespace GUI {
GenericDownloadDialog::GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
wxWindow* parent)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
, m_title(title)
, m_file_url(file_url)
, m_file_name(file_name)
, m_dest_path(dest_path)
{
std::string icon_path = (boost::format("%1%/images/Snapmaker_OrcaTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
setup_ui();
Bind(wxEVT_CLOSE_WINDOW, &GenericDownloadDialog::on_close, this);
wxGetApp().UpdateDlgDarkUI(this);
}
GenericDownloadDialog::~GenericDownloadDialog()
{
// Set destroying flag first to prevent any callbacks from accessing this object
m_is_destroying = true;
// Cancel any active download before destruction
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
}
void GenericDownloadDialog::setup_ui()
{
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
m_simplebook_status = new wxSimplebook(this);
m_simplebook_status->SetSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMinSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMaxSize(wxSize(FromDIP(420), FromDIP(250)));
// Progress page
m_status_bar = std::make_shared<BBLStatusBarSend>(m_simplebook_status);
m_panel_download = m_status_bar->get_panel();
m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70)));
// Complete page
m_panel_complete = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_complete = new wxBoxSizer(wxVERTICAL);
m_complete_text = new wxStaticText(m_panel_complete, wxID_ANY, _L("Download completed successfully!"),
wxDefaultPosition, wxDefaultSize, 0);
m_complete_text->SetForegroundColour(*wxBLACK);
m_complete_text->Wrap(FromDIP(360));
sizer_complete->Add(m_complete_text, 0, wxALIGN_CENTER | wxALL, 5);
StateColor btn_close_bg(std::pair<wxColour, int>(wxColour(0x90, 0x90, 0x90), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_close_bd(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_close_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_close_button = new Button(m_panel_complete, _L("Close"));
m_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetBackgroundColour(*wxWHITE);
m_close_button->SetBackgroundColor(btn_close_bg);
m_close_button->SetBorderColor(btn_close_bd);
m_close_button->SetTextColor(btn_close_txt);
m_close_button->SetCornerRadius(FromDIP(12));
m_close_button->SetCursor(wxCURSOR_HAND);
m_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_complete->Add(m_close_button, 0, wxALIGN_CENTER | wxALL, 5);
m_panel_complete->SetSizer(sizer_complete);
m_panel_complete->Layout();
sizer_complete->Fit(m_panel_complete);
// Error page
m_panel_error = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_error = new wxBoxSizer(wxVERTICAL);
// Simple label for error message display
m_error_text = new wxStaticText(m_panel_error, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxSize(FromDIP(380), -1),
wxALIGN_LEFT | wxST_ELLIPSIZE_END);
m_error_text->SetForegroundColour(*wxBLACK);
m_error_text->Wrap(FromDIP(380));
sizer_error->Add(m_error_text, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(10));
// Button sizer aligned to right
wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL);
sizer_buttons->AddStretchSpacer();
StateColor btn_retry_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Hovered), // Same as Normal
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Normal));
// Set border color to same as background to avoid corner color issues
StateColor btn_retry_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Enabled)); // Same as background
StateColor btn_retry_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_retry_button = new Button(m_panel_error, _L("Retry"));
m_retry_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_retry_button->SetBackgroundColour(*wxWHITE);
m_retry_button->SetBackgroundColor(btn_retry_bg);
m_retry_button->SetBorderColor(btn_retry_bg);
m_retry_button->SetTextColor(btn_retry_txt);
m_retry_button->SetCornerRadius(FromDIP(12));
m_retry_button->SetCursor(wxCURSOR_HAND);
m_retry_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_retry_clicked, this);
// Setup StateColor for determine button (gray style)
StateColor btn_determine_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_determine_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_determine_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_error_close_button = new Button(m_panel_error, _L("Determine"));
m_error_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_error_close_button->SetBackgroundColour(*wxWHITE);
m_error_close_button->SetBackgroundColor(btn_determine_bg);
m_error_close_button->SetBorderColor(btn_determine_bg);
m_error_close_button->SetTextColor(btn_determine_txt);
m_error_close_button->SetCornerRadius(FromDIP(12));
m_error_close_button->SetCursor(wxCURSOR_HAND);
m_error_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_buttons->Add(m_error_close_button, 0, 0);
sizer_buttons->AddSpacer(FromDIP(6));
sizer_buttons->Add(m_retry_button, 0, 0);
sizer_error->AddSpacer(FromDIP(40));
sizer_error->Add(sizer_buttons, 0, wxALIGN_RIGHT | wxRIGHT, FromDIP(24));
m_panel_error->SetSizer(sizer_error);
m_panel_error->Layout();
sizer_error->Fit(m_panel_error);
m_sizer_main->Add(m_simplebook_status, 0, wxALL, FromDIP(16));
m_simplebook_status->AddPage(m_panel_download, wxEmptyString, true);
m_simplebook_status->AddPage(m_panel_complete, wxEmptyString, false);
m_simplebook_status->AddPage(m_panel_error, wxEmptyString, false);
SetSizer(m_sizer_main);
Layout();
Fit();
CentreOnParent();
}
wxString GenericDownloadDialog::format_text(wxStaticText* st, wxString str, int warp)
{
if (wxGetApp().app_config->get("language") != "zh_CN") {
return str;
}
wxString out_txt = str;
wxString count_txt = "";
for (int i = 0; i < str.length(); i++) {
auto text_size = st->GetTextExtent(count_txt);
if (text_size.x < warp) {
count_txt += str[i];
} else {
out_txt.insert(i - 1, '\n');
count_txt = "";
}
}
return out_txt;
}
void GenericDownloadDialog::start_download()
{
show_progress_page();
m_status_bar->set_progress(0);
m_status_bar->set_status_text(_L("Preparing download..."));
m_status_bar->change_button_label(_L("Cancel"));
m_status_bar->set_cancel_callback_fina([this]() {
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(wxID_CANCEL);
});
// Create download callbacks
DownloadCallbacks callbacks;
callbacks.on_progress = [this](size_t task_id, int percent, size_t downloaded, size_t total) {
on_download_progress(task_id, percent, downloaded, total);
};
callbacks.on_complete = [this](size_t task_id, const std::string& file_path) {
on_download_complete(task_id, file_path);
};
callbacks.on_error = [this](size_t task_id, const std::string& error) {
on_download_error(task_id, error);
};
// Start download
if (m_dest_path.empty()) {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, std::move(callbacks));
} else {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, m_dest_path, std::move(callbacks));
}
}
int GenericDownloadDialog::ShowModal()
{
start_download();
return DPIDialog::ShowModal();
}
void GenericDownloadDialog::on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total)
{
wxGetApp().CallAfter([this, percent, downloaded, total]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
update_progress(percent);
// Format status text
wxString status_text;
if (total > 0) {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
double total_mb = total / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB / %.1f MB (%d%%)"),
downloaded_mb, total_mb, percent);
} else {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB..."), downloaded_mb);
}
m_status_bar->set_status_text(status_text);
// Call user callback if set
if (m_on_progress) {
m_on_progress(m_task_id, percent, downloaded, total);
}
});
}
void GenericDownloadDialog::on_download_complete(size_t task_id, const std::string& file_path)
{
wxGetApp().CallAfter([this, file_path]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = true;
m_file_path = file_path;
show_complete_page();
// Mark task as completed - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_complete) {
m_on_complete(m_task_id, file_path);
}
});
}
void GenericDownloadDialog::on_download_error(size_t task_id, const std::string& error)
{
// Log detailed error information for debugging
BOOST_LOG_TRIVIAL(error) << boost::format("GenericDownloadDialog: Download failed for file '%1%' from URL '%2%'. Error: %3%")
% m_file_name % m_file_url % error;
wxGetApp().CallAfter([this, error]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = false;
m_error_message = error;
show_error_page(error);
// Mark task as completed (failed) - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_error) {
m_on_error(m_task_id, error);
}
});
}
void GenericDownloadDialog::on_retry_clicked(wxCommandEvent& event)
{
SetTitle(m_title);
if (m_on_retry) {
m_on_retry();
}
start_download();
event.Skip();
}
void GenericDownloadDialog::on_close_clicked(wxCommandEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(m_download_success ? wxID_OK : wxID_CANCEL);
event.Skip();
}
void GenericDownloadDialog::on_close(wxCloseEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
event.Skip();
}
void GenericDownloadDialog::show_progress_page()
{
m_simplebook_status->SetSelection(0);
m_status_bar->set_progress(0);
m_status_bar->show_cancel_button();
}
void GenericDownloadDialog::show_complete_page()
{
//m_simplebook_status->SetSelection(1);
//m_status_bar->hide_cancel_button();
EndModal(wxID_OK);
}
void GenericDownloadDialog::show_error_page(const std::string& error_msg)
{
m_simplebook_status->SetSelection(2);
SetTitle(_L("Donwload failed"));
// Display simple error message: filename + "Download failed"
wxString filename = wxString::FromUTF8(m_file_name.c_str());
wxString error_text = filename + " - Download failed";
// Set error text in simple label
m_error_text->SetLabel(error_text);
m_error_text->Wrap(FromDIP(380));
m_panel_error->Layout();
m_simplebook_status->Layout();
Layout();
Fit();
m_status_bar->hide_cancel_button();
}
void GenericDownloadDialog::update_progress(int percent, const wxString& status_text)
{
m_status_bar->set_progress(percent);
if (!status_text.IsEmpty()) {
m_status_bar->set_status_text(status_text);
}
}
void GenericDownloadDialog::on_dpi_changed(const wxRect &suggested_rect)
{
// Handle DPI changes if needed
}
}} // namespace Slic3r::GUI
+113
View File
@@ -0,0 +1,113 @@
#ifndef slic3r_GenericDownloadDialog_hpp_
#define slic3r_GenericDownloadDialog_hpp_
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include "GUI_Utils.hpp"
#include <wx/dialog.h>
#include <wx/simplebook.h>
#include "BBLStatusBar.hpp"
#include "BBLStatusBarSend.hpp"
#include "Jobs/Worker.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
#include "Widgets/Button.hpp"
class wxBoxSizer;
class wxPanel;
class wxStaticText;
class wxHyperlinkCtrl;
namespace Slic3r {
namespace GUI {
// Generic download dialog for custom download tasks with progress display
class GenericDownloadDialog : public DPIDialog
{
public:
// Callback types
using DownloadCallback = std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)>;
using CompleteCallback = std::function<void(size_t task_id, const std::string& file_path)>;
using ErrorCallback = std::function<void(size_t task_id, const std::string& error)>;
using RetryCallback = std::function<void()>;
GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path = "",
wxWindow* parent = nullptr);
~GenericDownloadDialog();
// Start download
void start_download();
// Set callbacks (optional)
void set_on_progress(DownloadCallback callback) { m_on_progress = callback; }
void set_on_complete(CompleteCallback callback) { m_on_complete = callback; }
void set_on_error(ErrorCallback callback) { m_on_error = callback; }
void set_on_retry(RetryCallback callback) { m_on_retry = callback; }
// Get download result
bool is_success() const { return m_download_success; }
std::string get_file_path() const { return m_file_path; }
std::string get_error_message() const { return m_error_message; }
// Show modal and return result
int ShowModal() override;
protected:
void on_close(wxCloseEvent& event);
void on_dpi_changed(const wxRect &suggested_rect) override;
wxString format_text(wxStaticText* st, wxString str, int warp);
// Event handlers
void on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total);
void on_download_complete(size_t task_id, const std::string& file_path);
void on_download_error(size_t task_id, const std::string& error);
void on_retry_clicked(wxCommandEvent& event);
void on_close_clicked(wxCommandEvent& event);
private:
void setup_ui();
void show_progress_page();
void show_complete_page();
void show_error_page(const std::string& error_msg);
void update_progress(int percent, const wxString& status_text = "");
wxString m_title;
std::string m_file_url;
std::string m_file_name;
std::string m_dest_path;
size_t m_task_id{0};
bool m_download_success{false};
std::string m_file_path;
std::string m_error_message;
// Callbacks
DownloadCallback m_on_progress;
CompleteCallback m_on_complete;
ErrorCallback m_on_error;
RetryCallback m_on_retry;
// UI components
wxSimplebook* m_simplebook_status{nullptr};
std::shared_ptr<BBLStatusBarSend> m_status_bar;
wxPanel* m_panel_download{nullptr};
wxPanel* m_panel_complete{nullptr};
wxPanel* m_panel_error{nullptr};
wxStaticText* m_complete_text{nullptr};
wxStaticText* m_error_text{nullptr};
Button* m_retry_button{nullptr};
Button* m_close_button{nullptr};
Button* m_error_close_button{nullptr};
std::atomic<bool> m_is_destroying{false};
};
}} // namespace Slic3r::GUI
#endif // slic3r_GenericDownloadDialog_hpp_
+44 -2
View File
@@ -75,7 +75,7 @@
#endif // _WIN32
#include <slic3r/GUI/CreatePresetsDialog.hpp>
#include "sentry_wrapper/SentryWrapper.hpp"
#include "GenericDownloadDialog.hpp"
#define UPDATE_BUSER true
#define UPDATE_BUAUTO false
@@ -2260,7 +2260,8 @@ static wxMenu* generate_help_menu()
// //TODO
// });
// Check New Version
append_menu_item(helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
append_menu_item(
helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
[](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, UPDATE_BUSER);
}, "", nullptr, []() {
@@ -4020,6 +4021,47 @@ void MainFrame::RunScript(wxString js)
m_webview->RunScript(js);
}
void MainFrame::downloadOpenProject(const std::string& fileUrl, const std::string& fileName, std::string completeFilePath)
{
// std::string fileUrl = "https://public.resource.snapmaker.com/model/public/3mf/test_for_download.3mf";
// std::string filename = "test_for_download.3mf";
GenericDownloadDialog dlg(_L("downloading the model"), fileUrl, fileName, completeFilePath);
auto res = dlg.ShowModal();
if (res != wxID_OK)
return;
if (completeFilePath.empty()) {
auto downloadPath = wxGetApp().app_config->get("download_path");
completeFilePath = downloadPath + "/" + fileName;
}
if (!boost::filesystem::exists(completeFilePath))
{
BOOST_LOG_TRIVIAL(warning) << boost::format("the file '%1%' not exists") % completeFilePath;
return;
}
// Auto-open project if it's a .3mf file
boost::filesystem::path path(completeFilePath);
std::string extension = boost::algorithm::to_lower_copy(path.extension().string());
if (extension == ".3mf") {
BOOST_LOG_TRIVIAL(info) << boost::format("GenericDownloadDialog: Auto-opening project file '%1%'") % completeFilePath;
wxString wx_file_path = wxString::FromUTF8(completeFilePath.c_str());
if (wxGetApp().can_load_project() && wxGetApp().mainframe && wxGetApp().mainframe->plater()) {
wxGetApp().mainframe->plater()->load_project(wx_file_path);
}
}
else
{
// Not a valid 3mf file, show error message
wxString msg = wxString::Format(_L("The downloaded file '%s' is not a valid 3MF project file."), fileName);
MessageDialog(this, msg, _L("Invalid File"), wxOK | wxICON_WARNING).ShowModal();
}
}
void MainFrame::technology_changed()
{
// update menu titles
+5 -1
View File
@@ -348,7 +348,11 @@ public:
void load_printer_url();
bool is_printer_view() const;
void refresh_plugin_tips();
void RunScript(wxString js);
void RunScript(wxString js);
void downloadOpenProject(const std::string& fileUrl,
const std::string& fileName,
std::string completeFilePath = "");
//SoftFever
void show_device(bool bBBLPrinter);
+9 -11
View File
@@ -645,6 +645,10 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b
const std::string &opt_key = itOption.first;
int opt_index = itOption.second;
// SM Orca: Debug logging - track parameter changes from UI
BOOST_LOG_TRIVIAL(error) << "ConfigOptionsGroup::on_change_OG: opt_id=" << opt_id
<< ", opt_key=" << opt_key << ", opt_index=" << opt_index;
this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index);
}
@@ -1227,18 +1231,12 @@ void ExtruderOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const
auto itOption = it->second;
const std::string& opt_key = itOption.first;
int opt_index = itOption.second;
auto opt = m_config->option(opt_key);
const ConfigOptionVectorBase* opt_vec = dynamic_cast<const ConfigOptionVectorBase*>(opt);
if (opt_vec != nullptr) {
for (int opt_index = 0; opt_index < opt_vec->size(); opt_index++) {
this->change_opt_value(opt_key, value, opt_index);
}
}
else {
int opt_index = itOption.second;
this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index);
}
// SM Orca: FIX - Only modify the specific extruder's value, not all extruders
// The original code iterated through all indices and set them to the same value,
// which caused all extruders to have identical values when editing one extruder
this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index);
}
OptionsGroup::on_change_OG(opt_id, value);
-42
View File
@@ -13743,18 +13743,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
islegal = (c_preset == connect_preset);
/* if (!islegal) {
MessageDialog msg_window(nullptr,
_L(" Your connected machine is ") + (connect_preset == "" ? "Unknown" : connect_preset) + _L("\nYour model's preset is ") + c_preset + _L("\nDo you want to continue?"),
L("machine check"),
wxICON_QUESTION | wxOK);
int res = msg_window.ShowModal();
if (res != wxID_OK) {
return;
}
}*/
DynamicPrintConfig* physical_printer_config = &Slic3r::GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (! physical_printer_config || p->model.objects.empty())
return;
@@ -13773,34 +13761,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
local_name.erase(std::remove(local_name.begin(), local_name.end(), '('), local_name.end());
local_name.erase(std::remove(local_name.begin(), local_name.end(), ')'), local_name.end());
/*if (wxGetApp().app_config->get("use_new_connect") == "true") {
upload_job = PrintHostJob(wxGetApp().get_host_config());
} */
// if (local_name == "Snapmaker U1 0.4 nozzle" && devices.size() == 0) {
// MessageDialog msg_window(nullptr, _L("You don't have active machine, do you want to add one?"), _L("Info"), wxICON_QUESTION | wxOK | wxCANCEL);
// int res = msg_window.ShowModal();
// if (res == wxID_OK) {
// wxGetApp().mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
// auto view = wxGetApp().mainframe->m_printer_view;
// if (view) {
// json msg;
// msg["head"] = json::object();
// json payload = json::object();
// payload["cmd"] = "devicepage_add_device";
// payload["method"] = "call_flutter";
// payload["params"] = json::object();
// msg["payload"] = payload;
// std::string str_msg = msg.dump(4, ' ', true);
// view->sendMessage(str_msg);
// }
// }
// return;
// }
if (wxGetApp().app_config->get("use_new_connect") == "true" || local_name == "Snapmaker U1 0.4 nozzle") {
// 先不创建job,直接创建上传 / 上传下载对话框
// 获取默认文件名
@@ -13862,8 +13822,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
dialog->set_display_file_name(upload_job.upload_data.upload_path.string());
bool res = dialog->run();
// wxGetApp().mainframe->m_printer_view->reload();
if (dialog->is_finish()) {
wxGetApp().mainframe->select_tab(MainFrame::TabPosition::tpMonitor);
}
+88 -39
View File
@@ -2,7 +2,7 @@
#include "SSWCP.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "WCPDownloadManager.hpp"
#include "DownloadManager.hpp"
#include "nlohmann/json.hpp"
#include "slic3r/GUI/Tab.hpp"
#include "sentry_wrapper/SentryWrapper.hpp"
@@ -3005,10 +3005,11 @@ void SSWCP_MachineOption_Instance::sw_FinishFilamentMapping()
if (wxGetApp().get_web_preprint_dialog()) {
WebPreprintDialog* dialog = dynamic_cast<WebPreprintDialog*>(wxGetApp().get_web_preprint_dialog());
if (dialog) {
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
if(dialog->is_finish()){
dialog->EndModal(wxID_OK);
dialog->SafeEndModal(wxID_OK);
}else{
dialog->EndModal(wxID_CANCEL);
dialog->SafeEndModal(wxID_CANCEL);
}
}
}
@@ -3058,29 +3059,19 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping()
long long res = 0;
if ((oriclr.size() != 7 && oriclr.size() != 9) || oriclr[0] != '#') {
return -1;
return 0;
}
if (oriclr.size() == 7) {
for (int i = 1; i <= 6; ++i) {
if (oriclr[7 - i] - '0' >= 0 && oriclr[7 - i] - '0' <= 9) {
res += std::pow(16, i - 1) * (oriclr[7 - i] - '0');
} else {
res += std::pow(16, i - 1) * (oriclr[7 - i] - 'A' + 10);
}
}
} else {
for (int i = 1; i <= 8; ++i) {
if (oriclr[7 - i] - '0' >= 0 && oriclr[7 - i] - '0' <= 9) {
res += std::pow(16, i - 1) * (oriclr[7 - i] - '0');
} else {
res += std::pow(16, i - 1) * (oriclr[7 - i] - 'A' + 10);
}
}
auto colorSize = oriclr.size();//7 or 9
for (auto i = 1; i < colorSize; i++)
{
if (oriclr[colorSize - i] - '0' >= 0 && oriclr[colorSize - i] - '0' <= 9) {
res += std::pow(16, i - 1) * (oriclr[colorSize - i] - '0');
} else {
res += std::pow(16, i - 1) * (oriclr[colorSize - i] - 'A' + 10);
}
}
return res;
};
@@ -3190,14 +3181,10 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping()
response["thumbnails"] = thumbnails;
// file name
response["filename"] = SSWCP::get_display_filename();
response["filepath"] = SSWCP::get_active_filename();
m_res_data = response;
send_to_js();
finish_job();
@@ -4304,6 +4291,8 @@ void SSWCP_UserLogin_Instance::process()
sw_GetUserUpdatePrivacy();
} else if (m_cmd == DOWNLOAD_FILE) {
sw_DownloadFile();
} else if (m_cmd == DOWNLOAD_FILE_AND_OPEN) {
sw_DownloadFileAndOpen();
} else if (m_cmd == CANCEL_DOWNLOAD) {
sw_CancelDownload();
} else if (m_cmd == FILE_VIEW) {
@@ -4390,7 +4379,8 @@ void SSWCP_UserLogin_Instance::sw_GetUserUpdatePrivacy()
}
void SSWCP_UserLogin_Instance::sw_DownloadFile() {
void SSWCP_UserLogin_Instance::sw_DownloadFileAndOpen()
{
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
@@ -4400,28 +4390,86 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() {
return;
}
// Use WCP Download Manager
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager();
// Use Download Manager
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available");
handle_general_fail(-1, "Download Manager not available");
return;
}
// Start download task
size_t task_id = download_mgr->start_download(fileUrl, fileName, shared_from_this());
wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, "");
m_status = 0;
m_msg = "success";
send_to_js();
finish_job();
} catch (std::exception& e) {
handle_general_fail(-1, e.what());
}
}
void SSWCP_UserLogin_Instance::sw_DownloadFile()
{
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
if (fileUrl.empty() || fileName.empty()) {
handle_general_fail(-1, "file_url and file_name are required");
return;
}
// Use Download Manager
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "Download Manager not available");
return;
}
//only download file and don't do anything.
//wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, "");
m_status = 0;
m_msg = "success";
send_to_js();
finish_job();
} catch (std::exception& e) {
handle_general_fail(-1, e.what());
}
}
void SSWCP_UserLogin_Instance::sw_DownloadFileEx() {
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
if (fileUrl.empty() || fileName.empty()) {
handle_general_fail(-1, "file_url and file_name are required");
return;
}
// Use Download Manager
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "Download Manager not available");
return;
}
size_t task_id = download_mgr->start_wcp_download(fileUrl,
fileName,
shared_from_this(),
true);
// Return task ID to Flutter
json response;
response["task_id"] = task_id;
response["file_name"] = fileName;
response["file_url"] = fileUrl;
m_res_data = response;
m_status = 0;
m_msg = "Download started";
m_msg = "success";
send_to_js();
// Note: Do not call finish_job() here, as download is asynchronous
// The manager will send progress updates and completion/error messages via WCP
} catch (std::exception& e) {
handle_general_fail(-1, e.what());
}
@@ -4436,7 +4484,7 @@ void SSWCP_UserLogin_Instance::sw_CancelDownload() {
return;
}
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager();
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available");
return;
@@ -5996,7 +6044,8 @@ std::unordered_set<std::string> SSWCP::m_project_cmd_list = {
};
std::unordered_set<std::string> SSWCP::m_login_cmd_list = {"sw_UserLogin", "sw_UserLogout", "sw_GetUserLoginState", "sw_SubscribeUserLoginState",
UPDATE_PRIVACY_STATUS, GET_PRIVACY_STATUS};
UPDATE_PRIVACY_STATUS, GET_PRIVACY_STATUS,
DOWNLOAD_FILE,FILE_VIEW, CANCEL_DOWNLOAD, DOWNLOAD_FILE_AND_OPEN};
std::unordered_set<std::string> SSWCP::m_machine_manage_cmd_list = {
"sw_GetLocalDevices", "sw_AddDevice", "sw_SubscribeLocalDevices", "sw_RenameDevice", "sw_SwitchModel", "sw_DeleteDevices"
+6
View File
@@ -31,6 +31,7 @@ using tcp = asio::ip::tcp;
#define DELETE_CAMERA_TIMELAPSE "sw_DeleteCameraTimelapse"
#define GET_DEVICEDATA_STORAGESPACE "sw_GetDeviceDataStorageSpace"
#define DOWNLOAD_FILE "sw_DownloadFile"
#define DOWNLOAD_FILE_AND_OPEN "sw_DownLoadFileAndOpen"
#define CANCEL_DOWNLOAD "sw_CancelDownload"
#define FILE_VIEW "sw_FileView"
@@ -541,6 +542,11 @@ private:
void sw_SubUserUpdatePrivacy();
void sw_DownloadFile();
void sw_DownloadFileAndOpen();
void sw_DownloadFileEx();
void sw_CancelDownload();
void sw_FileView();
+40 -4
View File
@@ -3286,7 +3286,24 @@ void TabFilament::add_filament_overrides_page()
else {
const std::string printer_opt_key = opt_key.substr(strlen("filament_"));
const auto printer_config = m_preset_bundle->printers.get_edited_preset().config;
const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, opt_index);
// SM Orca: Map filament slot to physical extruder index for inheritance
auto& filament_extruder_map = wxGetApp().app_config->get_filament_extruder_map_ref();
// SM Orca: First calculate num_extruders to use modulo for default mapping
const ConfigOptionFloats* nozzle_diameter = printer_config.option<ConfigOptionFloats>("nozzle_diameter");
int num_extruders = nozzle_diameter ? (int)nozzle_diameter->values.size() : 1;
// SM Orca: Use modulo arithmetic for default mapping when no explicit mapping exists
int physical_extruder_idx = opt_index % num_extruders; // default: filament N maps to extruder N % num_extruders
auto map_it = filament_extruder_map.find(opt_index);
if (map_it != filament_extruder_map.end()) {
physical_extruder_idx = map_it->second;
}
// SM Orca: Bounds check to prevent crash from misconfigured map
if (physical_extruder_idx < 0 || physical_extruder_idx >= num_extruders) {
BOOST_LOG_TRIVIAL(warning) << "Invalid physical_extruder_idx " << physical_extruder_idx
<< " for filament slot " << opt_index << ", using default";
physical_extruder_idx = std::clamp(physical_extruder_idx, 0, num_extruders - 1);
}
const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, physical_extruder_idx);
field->update_na_value(printer_config_value);
field->set_na_value();
}
@@ -3301,7 +3318,7 @@ void TabFilament::add_filament_overrides_page()
optgroup->append_line(line);
};
const int extruder_idx = 0; // #ys_FIXME
const int extruder_idx = (m_presets_choice && m_presets_choice->get_filament_idx() >= 0) ? m_presets_choice->get_filament_idx() : 0; // SM Orca: Get actual filament slot index
for (const std::string opt_key : { "filament_retraction_length",
"filament_z_hop",
@@ -3367,7 +3384,7 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
// "filament_seam_gap"
};
const int extruder_idx = 0; // #ys_FIXME
const int extruder_idx = (m_presets_choice && m_presets_choice->get_filament_idx() >= 0) ? m_presets_choice->get_filament_idx() : 0; // SM Orca: Get actual filament slot index
const bool have_retract_length = m_config->option("filament_retraction_length")->is_nil() ||
m_config->opt_float("filament_retraction_length", extruder_idx) > 0;
@@ -3399,7 +3416,26 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
} else {
if (!is_checked) {
const std::string printer_opt_key = opt_key.substr(strlen("filament_"));
boost::any printer_config_value = optgroup->get_config_value(*printers_config, printer_opt_key, extruder_idx);
// SM Orca: Map filament slot to physical extruder index for inheritance
auto& filament_extruder_map = wxGetApp().app_config->get_filament_extruder_map_ref();
// SM Orca: Determine extruder count first for proper modulo calculation
const ConfigOptionFloats* nozzle_diameter = printers_config->option<ConfigOptionFloats>("nozzle_diameter");
int num_extruders = nozzle_diameter ? (int)nozzle_diameter->values.size() : 1;
int physical_extruder_idx = extruder_idx; // default: filament N uses extruder N
auto map_it = filament_extruder_map.find(extruder_idx);
if (map_it != filament_extruder_map.end()) {
physical_extruder_idx = map_it->second;
} else {
// SM Orca: Use modulo arithmetic when map entry doesn't exist
physical_extruder_idx = extruder_idx % num_extruders;
}
// SM Orca: Bounds check to prevent crash from misconfigured map
if (physical_extruder_idx < 0 || physical_extruder_idx >= num_extruders) {
BOOST_LOG_TRIVIAL(warning) << "Invalid physical_extruder_idx " << physical_extruder_idx
<< " for filament slot " << extruder_idx << ", using default";
physical_extruder_idx = std::clamp(physical_extruder_idx, 0, num_extruders - 1);
}
boost::any printer_config_value = optgroup->get_config_value(*printers_config, printer_opt_key, physical_extruder_idx);
field->update_na_value(printer_config_value);
field->set_value(printer_config_value, false);
}
-276
View File
@@ -1,276 +0,0 @@
#include "WCPDownloadManager.hpp"
#include "GUI_App.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
size_t WCPDownloadManager::start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
// Get download path
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
std::string dest_path = dest_file.string();
// Create task
auto task = std::make_shared<WCPDownloadTask>(task_id, file_url, file_name, dest_path, wcp_instance);
task->state = WCPDownloadState::Downloading;
m_tasks[task_id] = task;
// Start download
wxGetApp().CallAfter([this, task]() {
try {
// Step 1: Create Http object
Http http = Http::get(task->file_url);
// Step 2: Set progress callback
http.on_progress([this, task](Http::Progress progress, bool& cancel) {
if (task->state == WCPDownloadState::Canceled) {
cancel = true;
return;
}
// Calculate progress
int percent = 0;
if (progress.dltotal > 0) {
percent = (int)(progress.dlnow * 100 / progress.dltotal);
}
task->percent = percent;
// Throttle progress updates: update every 5% or every second
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto& last_pct = m_last_percent[task->task_id];
auto& last_upd = m_last_update[task->task_id];
auto now = std::chrono::steady_clock::now();
bool should_update = false;
if (percent - last_pct >= 5) {
should_update = true;
last_pct = percent;
} else if (now - last_upd >= std::chrono::seconds(1)) {
should_update = true;
}
if (should_update) {
last_upd = now;
wxGetApp().CallAfter([this, task, percent, progress]() {
send_progress_update(task, percent, progress.dlnow, progress.dltotal);
});
}
});
// Step 3: Set complete callback
http.on_complete([this, task](std::string body, unsigned status) {
wxGetApp().CallAfter([this, task, body]() {
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
send_error_update(task, "Failed to open file for writing");
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
file.close();
task->state = WCPDownloadState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
});
// Step 4: Set error callback
http.on_error([this, task](std::string body, std::string error, unsigned status) {
wxGetApp().CallAfter([this, task, error, status]() {
task->state = WCPDownloadState::Error;
task->error_message = error;
send_error_update(task, error);
cleanup_task(task->task_id);
});
});
// Step 5: Start download and save Http::Ptr for cancellation
task->http_object = http.perform();
} catch (std::exception& e) {
task->state = WCPDownloadState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
return task_id;
}
bool WCPDownloadManager::cancel_download(size_t task_id) {
std::shared_ptr<SSWCP_Instance> wcp_to_destroy;
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it == m_tasks.end()) {
return false;
}
auto task = it->second;
if (task->state == WCPDownloadState::Downloading) {
task->state = WCPDownloadState::Canceled;
if (task->http_object) {
task->http_object->cancel();
}
// Get WCP instance before cleanup (for destruction after lock release)
wcp_to_destroy = task->wcp_instance.lock();
cleanup_task(task_id);
} else {
return false;
}
}
// Destroy WCP instance outside the lock to prevent deadlock
// This is the WCP instance from the original download request (sw_DownloadFile)
if (wcp_to_destroy) {
wcp_to_destroy->finish_job();
}
return true;
}
bool WCPDownloadManager::pause_download(size_t task_id) {
// Pause functionality can be implemented if needed
// Current Http module may not support pause, need to implement resume from breakpoint
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Downloading) {
it->second->state = WCPDownloadState::Paused;
// Note: Http module doesn't support pause directly, would need breakpoint resume
return true;
}
return false;
}
bool WCPDownloadManager::resume_download(size_t task_id) {
// Resume functionality can be implemented if needed
// Would require breakpoint resume support in Http module
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Paused) {
// Would need to restart download with range header
return false; // Not implemented yet
}
return false;
}
WCPDownloadState WCPDownloadManager::get_task_state(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second->state;
}
return WCPDownloadState::Error;
}
std::shared_ptr<WCPDownloadTask> WCPDownloadManager::get_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second;
}
return nullptr;
}
void WCPDownloadManager::send_progress_update(std::shared_ptr<WCPDownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (auto wcp = task->wcp_instance.lock()) {
json progress_data;
progress_data["task_id"] = task->task_id;
progress_data["percent"] = percent;
progress_data["downloaded"] = downloaded;
progress_data["total"] = total;
progress_data["state"] = "downloading";
wcp->m_res_data = progress_data;
wcp->m_status = 0;
wcp->m_msg = "Download progress";
// Use progress event ID
json header;
header["event_id"] = wcp->m_event_id + "_progress";
header["command"] = "download_progress";
wcp->m_header = header;
wcp->send_to_js();
}
}
void WCPDownloadManager::send_complete_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& file_path) {
if (auto wcp = task->wcp_instance.lock()) {
json complete_data;
complete_data["task_id"] = task->task_id;
complete_data["file_path"] = file_path;
complete_data["file_name"] = task->file_name;
complete_data["percent"] = 100;
complete_data["state"] = "completed";
wcp->m_res_data = complete_data;
wcp->m_status = 0;
wcp->m_msg = "Download completed";
wcp->send_to_js();
// Release WCP instance to prevent memory leak
wcp->finish_job();
}
}
void WCPDownloadManager::send_error_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& error) {
if (auto wcp = task->wcp_instance.lock()) {
json error_data;
error_data["task_id"] = task->task_id;
error_data["error"] = error;
error_data["state"] = "error";
wcp->m_res_data = error_data;
wcp->m_status = -1;
wcp->m_msg = error;
wcp->send_to_js();
// Release WCP instance to prevent memory leak
wcp->finish_job();
}
}
void WCPDownloadManager::cleanup_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
}
}} // namespace Slic3r::GUI
-104
View File
@@ -1,104 +0,0 @@
#ifndef slic3r_WCPDownloadManager_hpp_
#define slic3r_WCPDownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state
enum class WCPDownloadState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download task information
struct WCPDownloadTask {
size_t task_id;
std::string file_url;
std::string file_name;
std::string dest_path;
std::weak_ptr<SSWCP_Instance> wcp_instance; // Associated WCP instance
Http::Ptr http_object; // HTTP object for cancellation
WCPDownloadState state;
int percent;
std::string error_message;
WCPDownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, std::shared_ptr<SSWCP_Instance> instance)
: task_id(id), file_url(url), file_name(name), dest_path(path),
wcp_instance(instance), state(WCPDownloadState::Pending), percent(0) {}
};
// WCP Download Manager
class WCPDownloadManager {
public:
static WCPDownloadManager& getInstance() {
static WCPDownloadManager instance;
return instance;
}
// Start a download task
size_t start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance);
// Cancel a download task
bool cancel_download(size_t task_id);
// Pause a download task (if needed)
bool pause_download(size_t task_id);
// Resume a download task (if needed)
bool resume_download(size_t task_id);
// Get task state
WCPDownloadState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<WCPDownloadTask> get_task(size_t task_id);
private:
WCPDownloadManager() = default;
~WCPDownloadManager() = default;
WCPDownloadManager(const WCPDownloadManager&) = delete;
WCPDownloadManager& operator=(const WCPDownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<WCPDownloadTask>> m_tasks;
std::atomic<size_t> m_next_task_id{1};
// Track last progress update for throttling
std::unordered_map<size_t, int> m_last_percent;
std::unordered_map<size_t, std::chrono::steady_clock::time_point> m_last_update;
// Send progress update to WCP
void send_progress_update(std::shared_ptr<WCPDownloadTask> task, int percent,
size_t downloaded, size_t total);
// Send completion message to WCP
void send_complete_update(std::shared_ptr<WCPDownloadTask> task, const std::string& file_path);
// Send error message to WCP
void send_error_update(std::shared_ptr<WCPDownloadTask> task, const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
};
}} // namespace Slic3r::GUI
#endif // slic3r_WCPDownloadManager_hpp_
+35 -3
View File
@@ -96,6 +96,22 @@ void WebPreprintDialog::set_display_file_name(const std::string& filename) {
void WebPreprintDialog::set_gcode_file_name(const std::string& filename)
{ m_gcode_file_name = filename; }
void WebPreprintDialog::set_finish(bool flag)
{
m_finish = flag;
// BBS: Don't call EndModal here to avoid conflict with sw_FinishFilamentMapping()
// The external sw_FinishFilamentMapping() function will handle EndModal based on m_finish flag
}
void WebPreprintDialog::SafeEndModal(int returnCode)
{
// BBS: Prevent duplicate EndModal calls which can cause crashes
if (IsModal() && !m_modal_ended) {
m_modal_ended = true;
EndModal(returnCode);
}
}
void WebPreprintDialog::reload()
{
load_url(m_prePrint_url);
@@ -123,8 +139,16 @@ bool WebPreprintDialog::run()
}
this->load_url(real_url);
if (this->ShowModal() == wxID_OK) {
return true;
// BBS: Reset flags before showing modal
m_finish = false;
m_modal_ended = false;
int result = this->ShowModal();
// BBS: Check finish flag to determine return value
if (result == wxID_OK || (result == wxID_CANCEL && m_finish)) {
return m_finish;
}
return false;
}
@@ -186,7 +210,15 @@ void WebPreprintDialog::OnClose(wxCloseEvent& evt)
{
auto noti_manager = wxGetApp().mainframe->plater()->get_notification_manager();
noti_manager->close_notification_of_type(NotificationType::PrintHostUpload);
evt.Skip();
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
// This ensures consistency with sw_FinishFilamentMapping() and prevents crashes
SafeEndModal(wxID_CANCEL);
// If not modal or already ended, skip the event
if (!IsModal() || m_modal_ended) {
evt.Skip();
}
}
}} // namespace Slic3r::GUI
+5 -1
View File
@@ -33,7 +33,10 @@ public:
bool is_finish() { return m_finish; }
void set_finish(bool flag) { m_finish = flag; }
void set_finish(bool flag);
// BBS: Safely end modal dialog, preventing duplicate EndModal calls
void SafeEndModal(int returnCode);
private:
void OnClose(wxCloseEvent& evt);
@@ -53,6 +56,7 @@ private:
bool m_switch_to_device = false;
bool m_finish = false;
bool m_modal_ended = false; // BBS: Flag to prevent duplicate EndModal calls
DECLARE_EVENT_TABLE()
};
+78 -74
View File
@@ -232,7 +232,11 @@ struct PresetUpdater::priv
void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file="");
void sync_config(bool isAuto_check = true);
void sync_update_flutter_resource(bool isAuto_check = true);
bool download_file(const std::string& url, const std::string& target_path, int timeout_sec = 30, bool* cancel_flag = nullptr);
bool download_file(const std::string& url,
const std::string& target_path,
const std::string& extract_path,
int timeout_sec = 30,
bool* cancel_flag = nullptr);
void sync_tooltip(std::string http_url, std::string language);
void sync_plugins(std::string http_url, std::string plugin_version);
void sync_printer_config(std::string http_url);
@@ -320,7 +324,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
{
bool res = true;
std::string file_path = source_path.string();
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
fs::path parent_path = !dest_path.empty() ? dest_path : source_path.parent_path();
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
@@ -331,6 +335,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
fs::path base_path = parent_path.lexically_normal();
mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
@@ -338,30 +343,48 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
std::string dest_file = parent_path+"/"+stat.m_filename;
if (stat.m_is_directory) {
fs::path dest_path(dest_file);
if (!fs::exists(dest_path))
fs::create_directories(dest_path);
continue;
fs::path full_dest = (base_path / stat.m_filename).lexically_normal();
// Reject paths that escape base (e.g. ".." in zip entry)
std::string rel_str = full_dest.lexically_relative(base_path).generic_string();
if (rel_str.empty() || rel_str.find("..") == 0) {
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: skip invalid path "<<stat.m_filename;
continue;
}
else if (stat.m_uncomp_size == 0) {
if (stat.m_is_directory) {
if (!fs::exists(full_dest))
fs::create_directories(full_dest);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
continue;
}
try
{
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0);
// Ensure parent directory exists (zip often has no directory entries, e.g. "flutter_web/version.json" only)
fs::path parent_dir = full_dest.parent_path();
if (!parent_dir.empty() && !fs::exists(parent_dir))
fs::create_directories(parent_dir);
std::string dest_file_encoded = encode_path(full_dest.string().c_str());
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file_encoded.c_str(), 0);
#ifdef _WIN32
if (!res) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed";
close_zip_reader(&archive);
return res;
std::wstring dest_file_w = boost::nowide::widen(full_dest.generic_string());
res = mz_zip_reader_extract_to_file_w(&archive, stat.m_file_index, dest_file_w.c_str(), 0);
}
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file;
#endif
if (!res) {
mz_zip_error zip_err = mz_zip_get_last_error(&archive);
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<full_dest.string()
<< " failed: " << (zip_err != MZ_ZIP_NO_ERROR ? mz_zip_get_error_string(zip_err) : "unknown");
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<full_dest.string();
}
catch (const std::exception& e)
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
return false;
@@ -373,7 +396,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
}
close_zip_reader(&archive);
return true;
return true;
}
// Remove leftover paritally downloaded files, if any.
@@ -656,7 +679,8 @@ void PresetUpdater::priv::sync_resources(std::string http_url, std::map<std::str
}
}
bool PresetUpdater::priv::download_file(const std::string& url,
const std::string& target_path,
const std::string& target_path,
const std::string& extract_path,
int timeout_sec,
bool* cancel_flag )
{
@@ -676,7 +700,7 @@ bool PresetUpdater::priv::download_file(const std::string& url,
.on_error([&url](std::string body, std::string error, unsigned http_status) {
BOOST_LOG_TRIVIAL(error) << "Download failed: " << url << ", HTTP status: " << http_status << ", error: " << error;
})
.on_complete([&](std::string body, unsigned http_status) {
.on_complete([&, target_path,tmp_path,extract_path](std::string body, unsigned http_status) {
if (http_status != 200) {
BOOST_LOG_TRIVIAL(error) << "Download failed with HTTP status: " << http_status;
return;
@@ -700,12 +724,12 @@ bool PresetUpdater::priv::download_file(const std::string& url,
BOOST_LOG_TRIVIAL(error) << "Failed to rename temp file: " << ec.message();
return;
}
extract_file(target_path, "../ota/profiles/");
extract_file(target_path, extract_path);
BOOST_LOG_TRIVIAL(info) << "Download completed: " << target_path;
res = true;
})
.timeout_max(timeout_sec)
.perform_sync();
.perform();
if (fs::exists(tmp_path)) {
fs::remove(tmp_path);
@@ -803,8 +827,19 @@ void PresetUpdater::priv::sync_update_flutter_resource(bool isAuto_check)
return;
}
if (currentPresetVersion < remoteVersion)
download_file(fileUrl, fileName);
if (currentPresetVersion < remoteVersion) {
if (fs::exists(fileName))
fs::remove(fileName);
fs::path tmpPath = fileName;
auto dirPath = tmpPath.parent_path() / "profiles/flutter_web";
if (fs::exists(dirPath))
fs::remove_all(dirPath);
download_file(fileUrl, fileName, "../ota/profiles/");
}
else {
if (!isAuto_check) {
wxCommandEvent* evt = new wxCommandEvent(EVT_NO_WEB_RESOURCE_UPDATE);
@@ -819,7 +854,7 @@ void PresetUpdater::priv::sync_update_flutter_resource(bool isAuto_check)
BOOST_LOG_TRIVIAL(fatal) << "request server flutter update data error:" << errorMsg;
}
})
.perform_sync();
.perform();
}
// Orca: sync config update for currect App version
void PresetUpdater::priv::sync_config(bool isAuto_check)
@@ -912,8 +947,18 @@ void PresetUpdater::priv::sync_config(bool isAuto_check)
return;
}
if (currentPresetVersion < remoteVersion)
download_file(fileUrl, fileName);
if (currentPresetVersion < remoteVersion) {
if (fs::exists(fileName))
fs::remove(fileName);
fs::path tmpPath = fileName;
auto dirPath = tmpPath.parent_path() / "profiles/profiles";
if (fs::exists(dirPath))
fs::remove_all(dirPath);
download_file(fileUrl, fileName, "../ota/profiles/profiles/");
}
else {
if (!isAuto_check) {
wxCommandEvent* evt = new wxCommandEvent(EVT_NO_PRESET_UPDATE);
@@ -928,7 +973,7 @@ void PresetUpdater::priv::sync_config(bool isAuto_check)
BOOST_LOG_TRIVIAL(fatal) << "request server preset update data error:" << errorMsg;
}
})
.perform_sync();
.perform();
}
void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string language)
@@ -1375,7 +1420,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
Updates updates;
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking for cached configuration updates...";
auto cache_profile_path = cache_path / "profiles";
auto cache_profile_path = cache_path / "profiles/profiles";
if (!fs::exists(cache_profile_path))
return updates;
@@ -1465,15 +1510,8 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
//BBS: switch to new BBL.json configs
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{
//std::string vendor_path;
//std::string vendor_name;
if (updates.incompats.size() > 0) {
//if (snapshot) {
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
// _u8L("Continue and install configuration updates?")))
// return false;
//}
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Deleting %1% incompatible bundles", updates.incompats.size());
for (auto &incompat : updates.incompats) {
@@ -1481,12 +1519,6 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
incompat.remove();
}
} else if (updates.updates.size() > 0) {
//if (snapshot) {
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
// _u8L("Continue and install configuration updates?")))
// return false;
//}
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Performing %1% updates", updates.updates.size());
@@ -1495,28 +1527,8 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
if (update.can_install)
update.install();
//if (!update.is_directory) {
// vendor_path = update.source.parent_path().string();
// vendor_name = update.vendor;
//}
}
//if (!vendor_path.empty()) {
// PresetBundle bundle;
// // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
// bundle.load_vendor_configs_from_json(vendor_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
// BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
// auto preset_remover = [](const Preset& preset) {
// BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
// fs::remove(preset.file);
// };
// for (const auto &preset : bundle.prints) { preset_remover(preset); }
// for (const auto &preset : bundle.filaments) { preset_remover(preset); }
// for (const auto &preset : bundle.printers) { preset_remover(preset); }
//}
}
return true;
@@ -1555,12 +1567,9 @@ PresetUpdater::~PresetUpdater()
//BBS: refine the preset updater logic
void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle *preset_bundle)
{
//p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{};
p->thread = std::thread([this, vendors, http_url, language, plugin_version]() {
@@ -1582,10 +1591,7 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string
return;
this->p->sync_plugins(http_url, plugin_version);
this->p->sync_printer_config(http_url);
//if (p->cancel)
// return;
//remove the tooltip currently
//this->p->sync_tooltip(http_url, language);
});
}
@@ -1603,9 +1609,7 @@ static bool reload_configs_update_gui()
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();