Merge remote-tracking branch 'upstream/main' into feedback

This commit is contained in:
Clifford Garwood
2026-05-17 20:21:23 -04:00
175 changed files with 37042 additions and 10898 deletions
+12
View File
@@ -172,6 +172,9 @@ typedef struct _sliced_info {
std::vector<sliced_plate_info_t> sliced_plates;
size_t prepare_time;
size_t export_time;
float layer_height{0.f};
float sparse_infill_density{0.f};
int wall_loops{0};
std::vector<std::string> upward_machines;
std::vector<std::string> downward_machines;
}sliced_info_t;
@@ -431,6 +434,9 @@ void record_exit_reson(std::string outputdir, int code, int plate_id, std::strin
j["error_string"] = error_message;
j["prepare_time"] = sliced_info.prepare_time;
j["export_time"] = sliced_info.export_time;
j["layer_height"] = sliced_info.layer_height;
j["wall_loops"] = sliced_info.wall_loops;
j["sparse_infill_density"] = sliced_info.sparse_infill_density;
for (size_t index = 0; index < sliced_info.sliced_plates.size(); index++)
{
json plate_json;
@@ -5898,6 +5904,12 @@ int CLI::run(int argc, char **argv)
DynamicPrintConfig new_print_config = m_print_config;
new_print_config.apply(*part_plate->config());
new_print_config.apply(m_extra_config, true);
if (m_print_config.option<ConfigOptionFloat>("layer_height"))
sliced_info.layer_height = m_print_config.option<ConfigOptionFloat>("layer_height")->value;
if (m_print_config.option<ConfigOptionInt>("wall_loops"))
sliced_info.wall_loops = m_print_config.option<ConfigOptionInt>("wall_loops")->value;
if (m_print_config.option<ConfigOptionPercent>("sparse_infill_density"))
sliced_info.sparse_infill_density = m_print_config.option<ConfigOptionPercent>("sparse_infill_density")->value;
if (new_extruder_count > 1) {
FilamentMapMode map_mode = fmmAutoForFlush;
if (new_print_config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode"))
+2 -7
View File
@@ -31,16 +31,11 @@ function(encoding_check TARGET)
# Define top-level encoding check target for this ${TARGET}
add_custom_target(encoding-check-${TARGET}
DEPENDS encoding-check ${T_SOURCES}
COMMAND $<TARGET_FILE:encoding-check> ${TARGET} ${T_SOURCES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Checking source files encodings for target ${TARGET}"
)
# Add checking of each source file as a subcommand of encoding-check-${TARGET}
foreach(file ${T_SOURCES})
add_custom_command(TARGET encoding-check-${TARGET} PRE_BUILD
COMMAND $<TARGET_FILE:encoding-check> ${TARGET} ${file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endforeach()
# This adds dependency on encoding-check-${TARGET} to ${TARET}
# via the global-encoding-check
+29 -18
View File
@@ -69,28 +69,24 @@ unsigned char *utf8_check(unsigned char *s)
}
int main(int argc, char const *argv[])
static const char* target;
void error_exit(const char* error, const char* filename)
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <program/library> <file>" << std::endl;
return -1;
}
std::cerr << "\n\tError: " << error << ": " << filename << "\n"
<< "\tTarget: " << target << "\n"
<< std::endl;
std::exit(-2);
}
const char* target = argv[1];
const char* filename = argv[2];
const auto error_exit = [=](const char* error) {
std::cerr << "\n\tError: " << error << ": " << filename << "\n"
<< "\tTarget: " << target << "\n"
<< std::endl;
std::exit(-2);
};
void utf8_check_file(const char* filename)
{
std::ifstream file(filename, std::ios::binary | std::ios::ate);
const auto size = file.tellg();
if (size == 0) {
return 0;
return;
}
file.seekg(0, std::ios::beg);
@@ -101,7 +97,7 @@ int main(int argc, char const *argv[])
// Check UTF-8 validity
if (utf8_check(reinterpret_cast<unsigned char*>(buffer.data())) != nullptr) {
error_exit("Source file does not contain (valid) UTF-8");
error_exit("Source file does not contain (valid) UTF-8", filename);
}
// Check against a BOM mark
@@ -109,10 +105,25 @@ int main(int argc, char const *argv[])
&& buffer[0] == '\xef'
&& buffer[1] == '\xbb'
&& buffer[2] == '\xbf') {
error_exit("Source file is valid UTF-8 but contains a BOM mark");
error_exit("Source file is valid UTF-8 but contains a BOM mark", filename);
}
} else {
error_exit("Could not read source file");
error_exit("Could not read source file", filename);
}
}
int main(int argc, char const *argv[])
{
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <program/library> <file[s]>" << std::endl;
return -1;
}
target = argv[1];
for (int i = 2; i < argc; i++) {
utf8_check_file(argv[i]);
}
return 0;
+3 -1
View File
@@ -834,8 +834,10 @@ std::string AppConfig::load()
void AppConfig::save()
{
if (! is_main_thread_active())
if (!is_main_thread_active()) {
BOOST_LOG_TRIVIAL(fatal) << "Calling AppConfig::save() from a worker thread!";
throw CriticalException("Calling AppConfig::save() from a worker thread!");
}
// The config is first written to a file with a PID suffix and then moved
// to avoid race conditions with multiple instances of Slic3r
+4
View File
@@ -330,6 +330,8 @@ public:
size_t hash() const throw() override { return std::hash<T>{}(this->value); }
// Warning mitigation: Indicate that virtual serialize() is not forgotten
using ConfigOption::serialize;
private:
friend class cereal::access;
template<class Archive> void serialize(Archive & ar) { ar(this->value); }
@@ -752,6 +754,8 @@ public:
return modified;
}
// Warning mitigation: Indicate that virtual serialize() is not forgotten
using ConfigOptionVectorBase::serialize;
private:
friend class cereal::access;
template<class Archive> void serialize(Archive & ar) { ar(this->values); }
+4 -3
View File
@@ -1,5 +1,6 @@
#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include "I18N.hpp"
#include "PrintConfig.hpp"
#include <algorithm>
#include <iomanip>
@@ -308,7 +309,7 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
{
// Only Klipper supports setting acceleration and jerk at the same time. Throw an error if we try to do this on other flavours.
if(FLAVOR_IS_NOT(gcfKlipper))
throw std::runtime_error("set_accel_and_jerk() is only supported by Klipper");
throw std::runtime_error(_u8L("set_accel_and_jerk() is only supported by Klipper"));
// Clamp the acceleration to the allowed maximum.
if (m_max_acceleration > 0 && acceleration > m_max_acceleration)
@@ -478,10 +479,10 @@ std::string GCodeWriter::set_input_shaping(char axis, float damp, float freq, st
break;
}
case gcfMarlinLegacy: {
throw std::runtime_error("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´");
throw std::runtime_error(_u8L("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´"));
}
default:
throw std::runtime_error("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2");
throw std::runtime_error(_u8L("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2"));
}
if (!gcode.str().empty()) {
if (GCodeWriter::full_gcode_comment) {
+72 -36
View File
@@ -27,6 +27,8 @@
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <miniz/miniz.h>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
// Mark string for localization and translate.
#define L(s) Slic3r::I18N::translate(s)
@@ -2188,48 +2190,82 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
vendor_name.erase(vendor_name.size() - 5);
vendor_names.push_back(vendor_name);
}
// Move ORCA_FILAMENT_LIBRARY to the beginning of the list
for (size_t i = 0; i < vendor_names.size(); ++ i) {
if (vendor_names[i] == ORCA_FILAMENT_LIBRARY) {
std::swap(vendor_names[0], vendor_names[i]);
break;
}
// Separate ORCA_FILAMENT_LIBRARY from other vendors. It must be loaded
// first because other vendors' filaments may inherit from it via the
// `base_bundle` lookup in parse_subfile. The remaining vendors are
// independent (no cross-vendor inheritance) and can be loaded in parallel.
std::string orca_lib_vendor;
std::vector<std::string> other_vendors;
other_vendors.reserve(vendor_names.size());
for (auto& vn : vendor_names) {
if (vn == ORCA_FILAMENT_LIBRARY)
orca_lib_vendor = vn;
else if (!(validation_mode && !vendor_to_validate.empty() && vn != vendor_to_validate))
other_vendors.push_back(vn);
}
for (auto &vendor_name : vendor_names)
{
if (validation_mode && !vendor_to_validate.empty() && vendor_name != vendor_to_validate && vendor_name != ORCA_FILAMENT_LIBRARY)
continue;
// Step 1: Load ORCA_FILAMENT_LIBRARY into `this` synchronously.
if (!orca_lib_vendor.empty()) {
try {
// Load the config bundle, flatten it.
if (first) {
// Reset this PresetBundle and load the first vendor config.
append(substitutions, this->load_vendor_configs_from_json(dir.string(), vendor_name, PresetBundle::LoadSystem, compatibility_rule).first);
first = false;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
PresetBundle other;
append(substitutions, other.load_vendor_configs_from_json(dir.string(), vendor_name, PresetBundle::LoadSystem, compatibility_rule, this).first);
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (!duplicates.empty()) {
errors_cummulative += "Found duplicated settings in vendor " + vendor_name + "'s json file lists: ";
for (size_t i = 0; i < duplicates.size(); ++i) {
if (i > 0)
errors_cummulative += ", ";
errors_cummulative += duplicates[i];
++m_errors;
BOOST_LOG_TRIVIAL(error) << "Found duplicated preset: " + duplicates[i] + " in vendor: " + vendor_name + ": ";
}
}
}
append(substitutions, this->load_vendor_configs_from_json(dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule).first);
first = false;
} catch (const std::runtime_error &err) {
if (validation_mode)
throw err;
else {
errors_cummulative += err.what();
errors_cummulative += "\n";
errors_cummulative += err.what();
errors_cummulative += "\n";
}
}
// Step 2: Load remaining vendors in parallel. Each gets its own
// PresetBundle and uses `this` (which contains ORCA_FILAMENT_LIBRARY)
// as the base_bundle for cross-bundle inheritance lookups.
std::vector<std::unique_ptr<PresetBundle>> parallel_bundles(other_vendors.size());
std::vector<PresetsConfigSubstitutions> parallel_substitutions(other_vendors.size());
std::vector<std::string> parallel_errors(other_vendors.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, other_vendors.size()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
auto bundle = std::make_unique<PresetBundle>();
try {
auto result = bundle->load_vendor_configs_from_json(
dir.string(), other_vendors[i], PresetBundle::LoadSystem,
compatibility_rule, this);
parallel_substitutions[i] = std::move(result.first);
parallel_bundles[i] = std::move(bundle);
} catch (const std::runtime_error &err) {
parallel_errors[i] = err.what();
}
}
});
// Step 3: Sequentially merge the parallel-loaded bundles into `this`.
// The merge order is the original vendor order so any duplicate-warning
// output stays stable across runs.
for (size_t i = 0; i < other_vendors.size(); ++i) {
if (!parallel_errors[i].empty()) {
if (validation_mode)
throw std::runtime_error(parallel_errors[i]);
errors_cummulative += parallel_errors[i];
errors_cummulative += "\n";
continue;
}
if (!parallel_bundles[i])
continue;
const std::string& vendor_name = other_vendors[i];
append(substitutions, std::move(parallel_substitutions[i]));
std::vector<std::string> duplicates = this->merge_presets(std::move(*parallel_bundles[i]));
first = false;
if (!duplicates.empty()) {
errors_cummulative += "Found duplicated settings in vendor " + vendor_name + "'s json file lists: ";
for (size_t j = 0; j < duplicates.size(); ++j) {
if (j > 0)
errors_cummulative += ", ";
errors_cummulative += duplicates[j];
++m_errors;
BOOST_LOG_TRIVIAL(error) << "Found duplicated preset: " + duplicates[j] + " in vendor: " + vendor_name + ": ";
}
}
}
+17 -2
View File
@@ -2446,6 +2446,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<FilamentMapMode>(fmmAutoForFlush));
def = this->add("enable_filament_dynamic_map", coBool);
def->label = L("Enable filament dynamic map");
def->tooltip = L("Enable dynamic filament mapping during print.");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("has_filament_switcher", coBool);
def->label = L("Has filament switcher");
def->tooltip = L("Printer has a filament switcher hardware (e.g., AMS).");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("filament_flush_temp", coInts);
def->label = L("Flush temperature");
def->tooltip = L("Temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range.");
@@ -4650,7 +4662,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("additional_fan_full_speed_layer", coInts);
def->label = L("Full fan speed at layer");
def->tooltip = L("Auxiliary fan speed will be ramped up linearly from layer \"For the first\" to maximum at layer \"Full fan speed at layer\". "
def->tooltip = L("Auxiliary fan speed will be ramped up linearly from layer \"For the first\" to maximum at layer \"Full fan speed at layer\".\n"
"\"Full fan speed at layer\" will be ignored if lower than \"For the first\", in which case the fan will run at maximum allowed speed at layer \"For the first\" + 1.");
def->min = 0;
def->max = 1000;
@@ -6330,7 +6342,10 @@ void PrintConfigDef::init_fff_params()
def = this->add("support_threshold_angle", coInt);
def->label = L("Threshold angle");
def->category = L("Support");
def->tooltip = L("Support will be generated for overhangs whose slope angle is below the threshold.");
def->tooltip = L("Support will be generated for overhangs whose slope angle is below the threshold. "
"The smaller this value is, the steeper the overhang that can be printed without support.\n"
"Note: If set to 0, normal supports use the Threshold overlap instead, "
"while tree supports fall back to a default value of 30.");
def->sidetext = u8"°"; // degrees, don't need translation
def->min = 0;
def->max = 90;
+10 -2
View File
@@ -3396,10 +3396,18 @@ static constexpr const std::initializer_list<const std::string_view> keys_extrud
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in)
{
// 1) Copy the "extruder key to sparse_infill_filament and wall_filament.
// 1) Map legacy "extruder" to feature filament keys as a fallback only.
// If any feature-specific filament is explicitly set, keep those values.
auto *opt_extruder = in.opt<ConfigOptionInt>(key_extruder);
auto *opt_sparse_infill_filament = in.opt<ConfigOptionInt>("sparse_infill_filament");
auto *opt_solid_infill_filament = in.opt<ConfigOptionInt>("solid_infill_filament");
auto *opt_wall_filament = in.opt<ConfigOptionInt>("wall_filament");
const bool has_feature_filament_override =
(opt_sparse_infill_filament != nullptr && opt_sparse_infill_filament->value > 0) ||
(opt_solid_infill_filament != nullptr && opt_solid_infill_filament->value > 0) ||
(opt_wall_filament != nullptr && opt_wall_filament->value > 0);
if (opt_extruder)
if (int extruder = opt_extruder->value; extruder != 0) {
if (int extruder = opt_extruder->value; extruder > 1 && ! has_feature_filament_override) {
// Not a default extruder.
out.sparse_infill_filament.value = extruder;
out.solid_infill_filament.value = extruder;
+36 -4
View File
@@ -1593,13 +1593,20 @@ void TreeSupport::generate_toolpaths()
}
else {
// base_areas
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() : support_flow;
bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0);
Flow flow = support_base_on_bed ? m_support_params.first_layer_flow : support_flow;
bool need_infill = with_infill;
if(m_object_config->support_base_pattern==smpDefault)
need_infill &= area_group.need_infill;
std::shared_ptr<Fill> filler_support = std::shared_ptr<Fill>(Fill::new_from_type(layer_id == 0 ? ipConcentric : m_support_params.base_fill_pattern));
// Orca: Use rectilinear for support base on the bed
const InfillPattern base_fill_pattern = support_base_on_bed ? ipRectilinear : m_support_params.base_fill_pattern;
std::shared_ptr<Fill> filler_support = std::shared_ptr<Fill>(Fill::new_from_type(base_fill_pattern));
filler_support->set_bounding_box(bbox_object);
filler_support->spacing = support_spacing * support_density; // constant spacing to align support infill lines
filler_support->spacing =
support_base_on_bed ?
flow.spacing() : // Orca: On the bed-contacting support base layer, use first-layer flow spacing directly.
support_spacing * support_density; // constant spacing to align support infill lines
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);
Polygons loops = to_polygons(poly);
@@ -1639,7 +1646,7 @@ void TreeSupport::generate_toolpaths()
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :support_flow;
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_support_params.first_layer_flow : support_flow;
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
for (auto& area : areas)
@@ -2342,6 +2349,31 @@ void TreeSupport::draw_circles()
floor_areas = std::move(new_floor_areas);
}
// Orca: Hybrid tree first-layer expansion belongs only to the normal-support
// part. area_poly is collected from ePolygon nodes above, which are the normal
// support nodes in Hybrid mode. Apply the expansion before area_groups and
// lslices are built so toolpaths and brim avoidance use the same footprint.
if (layer_nr == 0 && m_raft_layers == 0 && m_support_params.support_style == smsTreeHybrid &&
m_object_config->raft_first_layer_expansion.value > 0.f) {
ExPolygons expanded_base_areas;
const float inflate_factor_1st_layer = float(scale_(m_object_config->raft_first_layer_expansion.value));
Polygons trimming = offset(m_object->layers().front()->lslices, float(scale_(m_support_params.gap_xy_first_layer)),
SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Orca: Match normal support expansion: grow in steps and re-trim against the object each time.
const int nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width())));
const float step = inflate_factor_1st_layer / nsteps;
for (const ExPolygon &expoly : ts_layer->base_areas) {
if (overlaps({ expoly }, area_poly)) { // normal support in Hybrid mode
Polygons expanded = to_polygons(expoly);
for (int i = 0; i < nsteps; ++i)
expanded = diff(expand(expanded, step), trimming);
append(expanded_base_areas, union_ex(expanded));
} else
expanded_base_areas.emplace_back(expoly);
}
ts_layer->base_areas = std::move(expanded_base_areas);
}
auto &area_groups = ts_layer->area_groups;
for (auto& expoly : ts_layer->base_areas) {
+1 -1
View File
@@ -117,7 +117,7 @@ void set_logging_level(unsigned int level)
// Orca: force at info or lower level logging for pre-release builds.
// Note: not setting to debug or trace as they might affect long time usage especially with BBL printers.
const std::string version = SoftFever_VERSION;
if (level > (unsigned int) boost::log::trivial::info &&
if (level < (unsigned int) boost::log::trivial::info &&
(boost::algorithm::icontains(version, "dev") || boost::algorithm::icontains(version, "alpha") ||
boost::algorithm::icontains(version, "beta"))) {
logSeverity = boost::log::trivial::info;
+2
View File
@@ -392,6 +392,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Printer/PrinterFileSystem.cpp
GUI/Printer/PrinterFileSystem.h
GUI/PrinterWebView.cpp
GUI/PrinterWebViewHandler.cpp
GUI/PrinterWebViewHandler.hpp
GUI/PrinterWebView.hpp
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
+1 -6
View File
@@ -248,12 +248,7 @@ AboutDialog::AboutDialog()
wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format("Build %s", std::string(GIT_COMMIT_HASH)), wxDefaultPosition, wxDefaultSize);
credits_string->SetFont(_build_string_font);
wxFont version_font = GetFont();
#ifdef __WXMSW__
version_font.SetPointSize(version_font.GetPointSize()-1);
#else
version_font.SetPointSize(11);
#endif
version_font.SetPointSize(20);
version_font = version_font.Scaled(1.85f); // SetPointSize(20) not works on macOS because it uses a 72 PPI reference
version->SetFont(version_font);
version->SetForegroundColour(wxColour("#949494"));
credits_string->SetForegroundColour(wxColour("#949494"));
+4 -2
View File
@@ -653,7 +653,8 @@ void BBLTopbar::OnFileToolItem(wxAuiToolBarEvent& evt)
tb->SetToolSticky(evt.GetId(), true);
if (!m_skip_popup_file_menu) {
GetParent()->PopupMenu(m_file_menu, wxPoint(FromDIP(1), this->GetSize().GetHeight() - 2));
auto rec = this->GetToolRect(ID_TOP_FILE_MENU);
GetParent()->PopupMenu(m_file_menu, wxPoint(rec.GetLeft(), this->GetSize().GetHeight() - 2));
}
else {
m_skip_popup_file_menu = false;
@@ -670,7 +671,8 @@ void BBLTopbar::OnDropdownToolItem(wxAuiToolBarEvent& evt)
tb->SetToolSticky(evt.GetId(), true);
if (!m_skip_popup_dropdown_menu) {
GetParent()->PopupMenu(&m_top_menu, wxPoint(FromDIP(1), this->GetSize().GetHeight() - 2));
auto rec = this->GetToolRect(ID_TOP_DROPDOWN_MENU);
GetParent()->PopupMenu(&m_top_menu, wxPoint(rec.GetLeft(), this->GetSize().GetHeight() - 2));
}
else {
m_skip_popup_dropdown_menu = false;
+6 -4
View File
@@ -156,9 +156,11 @@ void BaseTransparentDPIFrame::on_hide()
m_refresh_timer->Stop();
}
Hide();
if (wxGetApp().mainframe != nullptr) {
wxGetApp().mainframe->Show();
wxGetApp().mainframe->Raise();
auto *mainframe = wxGetApp().mainframe;
if (mainframe != nullptr) {
if (!mainframe->IsShown())
mainframe->Show();
mainframe->Raise();
}
}
@@ -296,4 +298,4 @@ void BaseTransparentDPIFrame::deal_ok() {}
void BaseTransparentDPIFrame::deal_cancel(){}
}} // namespace Slic3r::GUI
}} // namespace Slic3r::GUI
+1
View File
@@ -151,6 +151,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
sizer_request->Add(0, 0, 0, wxTOP, FromDIP(5));
sizer_request->Add(ping_code_input, 0, wxLEFT, FromDIP(10));
sizer_request->Add(0, 0, 0, wxTOP, FromDIP(10));
sizer_request->AddStretchSpacer();
sizer_request->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
request_bind_panel->SetSizer(sizer_request);
request_bind_panel->Layout();
+24 -47
View File
@@ -1648,6 +1648,29 @@ bool CalibrationPresetPage::is_blocking_printing()
return false;
}
bool CalibrationPresetPage::is_nozzle_info_synced() const
{
if (!curr_obj || !curr_obj->is_info_ready())
return false;
for (const DevExtder& extruder : curr_obj->GetExtderSystem()->GetExtruders()) {
const int extruder_id = extruder.GetExtId();
if (!curr_obj->GetExtderSystem()->NozzleDiameterMatchesOrUnknown(extruder_id, get_nozzle_diameter(extruder_id)))
return false;
if (curr_obj->is_nozzle_flow_type_supported()) {
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE)
return false;
if (int(extruder.GetNozzleFlowType()) - 1 != int(get_nozzle_volume_type(extruder_id)))
return false;
}
}
return true;
}
void CalibrationPresetPage::update_sync_button_status()
{
auto set_status = [this](bool synced) {
@@ -1664,53 +1687,7 @@ void CalibrationPresetPage::update_sync_button_status()
}
};
if (!curr_obj || !curr_obj->is_info_ready()) {
set_status(false);
return;
}
struct CaliNozzleInfo
{
float nozzle_diameter{0.4f};
int nozzle_volume_type{0};
bool operator==(const CaliNozzleInfo &other) const
{
return abs(nozzle_diameter - other.nozzle_diameter) < EPSILON
&& nozzle_volume_type == other.nozzle_volume_type;
}
};
if (curr_obj->is_multi_extruders()) {
std::vector<CaliNozzleInfo> machine_obj_nozzle_infos;
machine_obj_nozzle_infos.resize(2);
for (const DevExtder& extruder : curr_obj->GetExtderSystem()->GetExtruders()) {
machine_obj_nozzle_infos[extruder.GetExtId()].nozzle_diameter = extruder.GetNozzleDiameter();
machine_obj_nozzle_infos[extruder.GetExtId()].nozzle_volume_type = int(extruder.GetNozzleFlowType()) - 1;
}
std::vector<CaliNozzleInfo> cali_nozzle_infos;
cali_nozzle_infos.resize(2);
for (size_t extruder_id = 0; extruder_id < 2; ++extruder_id) {
cali_nozzle_infos[extruder_id].nozzle_diameter = get_nozzle_diameter(extruder_id);
cali_nozzle_infos[extruder_id].nozzle_volume_type = int(get_nozzle_volume_type(extruder_id));
}
if (machine_obj_nozzle_infos == cali_nozzle_infos) {
set_status(true);
}
else {
set_status(false);
}
}
else {
if (abs(curr_obj->GetExtderSystem()->GetNozzleDiameter(0) - get_nozzle_diameter(0)) < EPSILON) {
set_status(true);
}
else {
set_status(false);
}
}
set_status(is_nozzle_info_synced());
}
void CalibrationPresetPage::update_show_status()
@@ -265,6 +265,7 @@ protected:
void update_combobox_filaments(MachineObject* obj);
void update_show_status();
void update_sync_button_status();
bool is_nozzle_info_synced() const;
void show_status(CaliPresetPageStatus status);
void Enable_Send_Button(bool enable);
bool is_blocking_printing();
+16 -13
View File
@@ -546,8 +546,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector<std::string> const &keys, std::map<ObjectBase *, ModelConfig *> const &configs)
{
for (auto &k : keys) {
if (/*k == "adaptive_layer_height" || */ k == "independent_support_layer_height" || k == "enable_support" ||
k == "detect_thin_wall" || k == "tree_support_adaptive_layer_height")
if (k == "independent_support_layer_height" || k == "enable_support" || k == "detect_thin_wall")
config->set_key_value(k, new ConfigOptionBool(true));
else if (k == "wall_loops")
config->set_key_value(k, new ConfigOptionInt(0));
@@ -742,19 +741,20 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
//toggle_field("support_closing_radius", have_support_material && support_style == smsSnug);
bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type);
bool support_is_normal_tree = support_is_tree && support_style != smsTreeOrganic &&
// Orca: use organic as default
support_style != smsDefault;
bool support_is_organic = support_is_tree && !support_is_normal_tree;
// settings shared by normal and organic trees
for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter" })
toggle_line(el, support_is_normal_tree);
bool support_is_organic = support_is_tree && (support_style == smsTreeOrganic || support_style == smsDefault);
bool support_is_normal_tree = support_is_tree && !support_is_organic;
// hide settings that are not used by tree supports
toggle_line("support_threshold_overlap", !support_is_tree); // ORCA: tree supports do not use Threshold Overlap
// settings specific to normal trees
for (auto el : {"tree_support_auto_brim", "tree_support_brim_width", "tree_support_adaptive_layer_height"})
for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "tree_support_auto_brim", "tree_support_brim_width"})
toggle_line(el, support_is_normal_tree);
// settings specific to organic trees
for (auto el : {"tree_support_branch_angle_organic", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_angle_slow", "tree_support_tip_diameter", "tree_support_top_rate", "tree_support_branch_diameter_angle"})
toggle_line(el, support_is_organic);
// ORCA: Independent support layer height is not compatible with organic tree supports,
// as they rely on the support layers being the same as the object layers to determine where to place branches.
toggle_line("independent_support_layer_height", have_support_material && !support_is_organic);
toggle_field("tree_support_brim_width", support_is_tree && !config->opt_bool("tree_support_auto_brim"));
// tree support use max_bridge_length instead of bridge_no_support
@@ -788,9 +788,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("raft_contact_distance", have_raft && !have_support_soluble);
// Orca: Raft, grid, snug and organic supports use these two parameters to control the size & density of the "brim"/flange
for (auto el : { "raft_first_layer_expansion", "raft_first_layer_density"})
toggle_field(el, have_support_material && !(support_is_normal_tree && !have_raft));
// Orca: First-layer density is available for supports broadly.
toggle_field("raft_first_layer_density", have_support_material);
// Orca: For regular tree (Slim/Strong) without raft, hide first-layer expansion.
// Keep it enabled for non-tree supports, organic tree, hybrid tree, and any raft case.
toggle_field("raft_first_layer_expansion",
have_support_material && ((!support_is_normal_tree || support_style == smsTreeHybrid) || have_raft));
bool has_ironing = (config->opt_enum<IroningType>("ironing_type") != IroningType::NoIroning);
for (auto el : { "ironing_pattern", "ironing_flow", "ironing_spacing", "ironing_angle", "ironing_inset", "ironing_angle_fixed" })
+24 -3
View File
@@ -3378,9 +3378,30 @@ bool CreatePrinterPresetDialog::validate_input_valid()
if (auto preset = wxGetApp().preset_bundle->printers.find_preset(custom_printer_name)) {
if (preset->is_system) {
MessageDialog dlg(this, _L("The system preset does not allow creation. \nPlease re-enter the printer model or nozzle diameter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
dlg.ShowModal();
// ORCA offer switcing to existing preset to reduce confusion
std::string diameters;
auto printer_model = preset->config.opt_string("printer_model");
for (auto &preset : wxGetApp().preset_bundle->printers) {
if (preset.config.opt_string("printer_model") == printer_model)
diameters += preset.config.opt_string("printer_variant") + " ";
}
MessageDialog dlg(this, _L("The system preset does not allow creation. \nPlease re-enter the printer model or nozzle diameter.")
+ _L("\n\nAvailable nozzle profiles for this printer:")
+ "\n" + diameters
+ _L("\n\nChoose YES to switch existing preset:")
+ "\n" + preset->name
, wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info")
, wxYES | wxYES_DEFAULT | wxNO | wxCENTRE);
if (dlg.ShowModal() == wxID_YES){
auto bundle = wxGetApp().preset_bundle;
bundle->printers.select_preset_by_name(preset->name, true);
bundle->update_compatible(PresetSelectCompatibleType::Always);
wxGetApp().load_current_presets();
wxGetApp().mainframe->update_side_preset_ui();
wxGetApp().sidebar().update_ui_from_settings();
wxGetApp().sidebar().update_all_preset_comboboxes();
EndModal(wxID_CANCEL);
}
return false;
}
}
@@ -6,6 +6,7 @@
#include <wx/string.h>
#include "DevDefs.h"
#include <cmath>
namespace Slic3r
{
@@ -145,6 +146,15 @@ public:
float GetNozzleDiameter(int extder_id) const { return GetExtderById(extder_id) ? GetExtderById(extder_id)->GetNozzleDiameter() : 0.0; }
int GetNozzleTempCurrent(int extder_id) const { return GetExtderById(extder_id) ? GetExtderById(extder_id)->GetCurrentTemp() : 0; }
int GetNozzleTempTarget(int extder_id) const { return GetExtderById(extder_id) ? GetExtderById(extder_id)->GetTargetTemp() : 0; }
bool NozzleDiameterMatchesOrUnknown(int extder_id, float target_diameter) const
{
float diameter = GetNozzleDiameter(extder_id);
if (diameter == 0.0f) {
return true;
} else {
return std::fabs(diameter - target_diameter) < 1e-3;
}
}
// get slot info which is connected to the extruder
std::string GetCurrentAmsId() const;
+7
View File
@@ -493,6 +493,13 @@ bool MachineObject::is_series_p() const { return is_series_p(DevPrinterConfigUti
bool MachineObject::is_series_x() const { return is_series_x(DevPrinterConfigUtil::get_printer_series_str(printer_type)); }
bool MachineObject::is_series_o() const { return is_series_o(DevPrinterConfigUtil::get_printer_series_str(printer_type)); }
bool MachineObject::can_use_emmc_print() const
{
AppConfig* config = GUI::wxGetApp().app_config;
std::string disable_emmc_print = config ? config->get("disable_emmc_print") : "true";
return is_support_print_with_emmc && (disable_emmc_print == "0" || disable_emmc_print == "false");
}
std::string MachineObject::get_printer_series_str() const{ return DevPrinterConfigUtil::get_printer_series_str(printer_type);};
void MachineObject::reload_printer_settings()
+1
View File
@@ -218,6 +218,7 @@ public:
bool is_series_p() const;
bool is_series_x() const;
bool is_series_o() const;
bool can_use_emmc_print() const;
void reload_printer_settings();
std::string get_printer_thumbnail_img_str() const;
+6 -16
View File
@@ -73,7 +73,7 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
wxString color_name = m_color_query->GetFilaColorName(fila_id, fila_color);
m_cur_color_name = new wxString(color_name);
wxBoxSizer *container_sizer = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *container_sizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
// Preview panel (always present)
@@ -94,16 +94,16 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
// "More colours" button (always present)
CreateMoreInfoButton();
main_sizer->Add(m_more_btn, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(8));
main_sizer->AddSpacer(FromDIP(8));
main_sizer->Add(m_more_btn, 0, wxEXPAND | wxTOP, FromDIP(8));
//main_sizer->AddSpacer(FromDIP(8));
// OK / Cancel buttons
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
m_ok_btn = dlg_btns->GetOK();
m_cancel_btn = dlg_btns->GetCANCEL();
main_sizer->Add(dlg_btns, 0, wxEXPAND);
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(16));
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
container_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizer(container_sizer);
Layout();
@@ -533,17 +533,7 @@ void FilamentPickerDialog::UpdateButtonStates(wxBitmapButton* selected_btn)
void FilamentPickerDialog::CreateMoreInfoButton()
{
m_more_btn = new Button(this, "+ " + _L("More Colors"));
m_more_btn->SetMinSize(wxSize(-1, FromDIP(36)));
StateColor btn_bg(
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(248, 248, 248), StateColor::Normal)
);
m_more_btn->SetBackgroundColor(btn_bg);
m_more_btn->SetBorderStyle(wxPENSTYLE_SHORT_DASH);
m_more_btn->SetCornerRadius(FromDIP(0));
m_more_btn->SetStyle(ButtonStyle::Regular, ButtonType::Expanded);
}
wxColourData FilamentPickerDialog::GetSingleColorData()
+255 -266
View File
@@ -30,9 +30,8 @@
#include "Widgets/ProgressDialog.hpp"
#include "MsgDialog.hpp"
#if ENABLE_ACTUAL_SPEED_DEBUG
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // ENABLE_ACTUAL_SPEED_DEBUG
#include <imgui/imgui_internal.h>
#include <glad/gl.h>
@@ -304,8 +303,6 @@ static std::string to_string(libvgcode::EGCodeExtrusionRole role)
void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode::Viewer* viewer, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type)
{
static float last_window_width = 0.0f;
static size_t last_text_length = 0;
static bool properties_shown = false;
const std::string NA_TXT = _u8L("N/A");
@@ -323,199 +320,281 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding , ImVec2(10.f, 10.f) * m_scale);
ImGui::SetNextWindowBgAlpha(0.8f);
imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
ImGui::AlignTextToFramePadding();
// ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, _u8L("Position") + ":");
// ImGui::SameLine();
libvgcode::PathVertex vertex = viewer->get_current_vertex();
size_t vertex_id = viewer->get_current_vertex_id();
if (view_type != libvgcode::EViewType::FeatureType && vertex.type == libvgcode::EMoveType::Seam) { // exclude FeatureType for proper type readings
vertex_id = static_cast<size_t>(viewer->get_view_visible_range()[1]) - 1;
vertex = viewer->get_vertex_at(vertex_id);
// Seam moves may contain non-deterministic vertex data, so use the
// last visible non-seam vertex instead (except in FeatureType view)
if (view_type != libvgcode::EViewType::FeatureType && vertex.type == libvgcode::EMoveType::Seam) {
const libvgcode::Interval& visible_range = viewer->get_view_visible_range();
if (visible_range[1] > 0) {
vertex_id = static_cast<size_t>(visible_range[1]) - 1;
vertex = viewer->get_vertex_at(vertex_id);
}
}
const bool is_extrusion = vertex.is_extrusion();
const ImGuiStyle& style = ImGui::GetStyle();
char detail_buf[1024];
switch (view_type) {
case libvgcode::EViewType::FeatureType:
if (is_extrusion)
strcpy(detail_buf, to_string(vertex.role).c_str());
else if (vertex.type != libvgcode::EMoveType::Noop)
strcpy(detail_buf, to_string(vertex.type).c_str());
else
strcpy(detail_buf, NA_CSTR); // Noop moves are not shown in the "Line type" view, so show N/A for them.
break;
case libvgcode::EViewType::Height:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Height: ").c_str(), vertex.height);
else
sprintf(detail_buf, "%s%s", _u8L("Height: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::Width:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Width: ").c_str(), vertex.width);
else
sprintf(detail_buf, "%s%s", _u8L("Width: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::VolumetricFlowRate:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Flow: ").c_str(), vertex.volumetric_rate());
else
sprintf(detail_buf, "%s%s", _u8L("Flow: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::FanSpeed:
sprintf(detail_buf, "%s%.0f", _u8L("Fan: ").c_str(), vertex.fan_speed);
break;
case libvgcode::EViewType::Temperature:
sprintf(detail_buf, "%s%.0f", _u8L("Temperature: ").c_str(), vertex.temperature);
break;
case libvgcode::EViewType::LayerTimeLinear:
case libvgcode::EViewType::LayerTimeLogarithmic:
sprintf(detail_buf, "%s%.1f", _u8L("Layer Time: ").c_str(), vertex.layer_duration);
break;
case libvgcode::EViewType::Tool:
sprintf(detail_buf, "%s%d", _u8L("Tool: ").c_str(), vertex.extruder_id + 1);
break;
case libvgcode::EViewType::ColorPrint:
sprintf(detail_buf, "%s%d", _u8L("Color: ").c_str(), vertex.color_id + 1);
break;
case libvgcode::EViewType::Acceleration:
sprintf(detail_buf, "%s%.0f", _u8L("Acceleration: ").c_str(), vertex.acceleration);
break;
case libvgcode::EViewType::Jerk:
sprintf(detail_buf, "%s%.1f", _u8L("Jerk: ").c_str(), vertex.jerk);
break;
case libvgcode::EViewType::PressureAdvance:
sprintf(detail_buf, "%s%.4f", _u8L("PA: ").c_str(), vertex.pressure_advance);
break;
default:
detail_buf[0] = '\0';
break;
}
// ORCA Moved position and information to bottom
std::vector<std::pair<std::string, std::string>> properties_rows;
float table_content_w = 0.0f;
if (properties_shown) {
float label_w = 0.0f;
float value_w = 0.0f;
properties_rows.reserve(13);
auto add_row = [&properties_rows, &label_w, &value_w](std::string label, std::string value) {
label_w = std::max(label_w, ImGui::CalcTextSize(label.c_str()).x);
value_w = std::max(value_w, ImGui::CalcTextSize(value.c_str()).x);
properties_rows.emplace_back(std::move(label), std::move(value));
};
char buff[1024];
add_row(_u8L("Type"), _u8L(to_string(vertex.type)));
add_row(_u8L("Line Type"), is_extrusion ? _u8L(to_string(vertex.role)) : NA_TXT);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.width); else strcpy(buff, NA_CSTR);
add_row(_u8L("Width"), buff);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height); else strcpy(buff, NA_CSTR);
add_row(_u8L("Height"), buff);
sprintf(buff, "%d", vertex.layer_id + 1);
add_row(_u8L("Layer"), buff);
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
add_row(_u8L("Speed"), buff);
sprintf(buff, ("%.0f " + _u8L("mm/s²")).c_str(), vertex.acceleration);
add_row(_u8L("Acceleration"), buff);
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.jerk);
add_row(_u8L("Jerk"), buff);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm³/s")).c_str(), vertex.volumetric_rate()); else strcpy(buff, NA_CSTR);
add_row(_u8L("Flow rate"), buff);
sprintf(buff, "%.0f %%", vertex.fan_speed);
add_row(_u8L("Fan speed"), buff);
sprintf(buff, ("%.0f " + _u8L("°C")).c_str(), vertex.temperature);
add_row(_u8L("Temperature"), buff);
sprintf(buff, "%.4f", vertex.pressure_advance);
add_row(_u8L("Pressure Advance"), buff);
const float estimated_time = viewer->get_estimated_time_at(vertex_id);
sprintf(buff, "%s (%.3fs)", get_time_dhms(estimated_time).c_str(), vertex.times[static_cast<size_t>(viewer->get_time_mode())]);
add_row(_u8L("Time"), buff);
table_content_w = 2 * std::max(label_w, value_w) + style.ItemInnerSpacing.x;
}
// "ToolPosition" layout width calculation
const float axes_spacing_x = ImGui::CalcTextSize("sp").x; // Spacing between axes labels
const float axes_width = ImGui::CalcTextSize("X 999.999").x; // Width of the longest possible axis label
const float axes_content_w = 3.0f * axes_width + 2.0f * axes_spacing_x; // Three axes
const float speed_width = ImGui::CalcTextSize((_u8L("Speed: ") + "9999 ").c_str()).x; // Width of the longest possible speed label
const float detail_width = ImGui::CalcTextSize(detail_buf).x; // Width of the detail text
const float info_content_w = speed_width + detail_width; // Speed + detail
const float info_group_w = std::max(axes_content_w, info_content_w); // The width of the group containing position and detail info, whichever is wider
const float fold_button_w = 24.0f * m_scale; // Width of the fold/unfold button
const float bottom_row_w = fold_button_w + style.ItemSpacing.x + info_group_w; // The width of the bottom row containing the fold button and the info group
float speed_profile_row_w = 0.0f;
#if ENABLE_ACTUAL_SPEED_DEBUG
const float table_btn_width = std::max(ImGui::CalcTextSize(_u8L("Hide").c_str()).x, ImGui::CalcTextSize(_u8L("Show").c_str()).x);
const float btn_padding_x = 8.0f * m_scale;
if (properties_shown) {
auto append_table_row = [](const std::string& label, std::function<void(void)> value_callback) {
// The width of the row containing the button to show/hide the actual speed profile table and its label
speed_profile_row_w = table_btn_width + btn_padding_x * 2.0f + style.ItemSpacing.x + ImGui::CalcTextSize(_u8L("Actual speed profile").c_str()).x;
}
#endif // ENABLE_ACTUAL_SPEED_DEBUG
const float properties_w = std::max(table_content_w, speed_profile_row_w); // The width of the properties section, whichever is wider between the properties table and the speed profile row
const float content_w = std::max(bottom_row_w, properties_w); // The width of the window content, whichever is wider between the bottom row and the properties table
const float window_w = std::ceil(content_w + 2.0f * style.WindowPadding.x); // Final window width with padding, rounded up for better look
// "ToolPosition" layout height calculation
const float text_h = ImGui::GetTextLineHeight();
const float item_spacing_y = style.ItemSpacing.y;
const float cell_pad_y = style.CellPadding.y;
const float window_pad_h = 2.0f * style.WindowPadding.y;
const float show_button_h = text_h + 2.0f * 3.0f * m_scale; // ImGuiStyleVar_FramePadding.y is set to 3.f * m_scale
const float main_row_h = 2.0f * text_h + item_spacing_y; // Two lines of text (position and detail) + spacing between them
const float properties_h = static_cast<float>(properties_rows.size()) * (text_h + 2.0f * cell_pad_y) + 2.0f * cell_pad_y + 1.0f + item_spacing_y // table rows
+ item_spacing_y + show_button_h // Spacing() + Show/Hide button row
+ item_spacing_y + 1.0f + style.FramePadding.y; // Spacing() + Separator() + Dummy()
const float folded_window_h = std::ceil(window_pad_h + main_row_h); // Height of the window when properties are hidden, with padding, rounded up for better look
const float unfolded_window_h = std::ceil(folded_window_h + properties_h); // Height of the window when properties are shown, with padding, rounded up for better look
const float window_h = properties_shown ? unfolded_window_h : folded_window_h; // Final window height depending on whether properties are shown or not
ImGui::SetNextWindowSize(ImVec2(window_w, window_h), ImGuiCond_Always);
imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
ImGui::AlignTextToFramePadding();
if (properties_shown) {
auto append_table_row = [](const std::string& label, const std::string& value) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, label);
ImGui::TableSetColumnIndex(1);
value_callback();
ImGuiWrapper::text(value);
};
//ImGui::Separator();
if (ImGui::BeginTable("Properties", 2)) {
char buff[1024];
append_table_row(_u8L("Type"), [&vertex]() {
ImGuiWrapper::text(_u8L(to_string(vertex.type)));
});
append_table_row(_u8L("Line Type"), [&vertex, NA_TXT]() {
std::string text;
if (vertex.is_extrusion())
text = _u8L(to_string(vertex.role));
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Width"), [&vertex, &buff, NA_TXT]() {
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.width);
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Height"), [&vertex, &buff, NA_TXT]() {
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height);
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Layer"), [&vertex, &buff]() {
sprintf(buff, "%d", vertex.layer_id + 1);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Speed"), [&vertex, &buff]() {
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Acceleration"), [&vertex, &buff]() {
sprintf(buff, ("%.0f " + _u8L("mm/s²")).c_str(), vertex.acceleration);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Jerk"), [&vertex, &buff]() {
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.jerk);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Flow rate"), [&vertex, &buff, NA_TXT]() { // ORCA use "Flow rate" instead "Volumetric flow Rate" to make window more compact
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm³/s")).c_str(), vertex.volumetric_rate());
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Fan speed"), [&vertex, &buff]() {
sprintf(buff, "%.0f %%", vertex.fan_speed);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Temperature"), [&vertex, &buff]() {
sprintf(buff, ("%.0f " + _u8L("°C")).c_str(), vertex.temperature);
ImGuiWrapper::text(std::string(buff));
});
// ORCA: Add Pressure Advance visualization support
append_table_row(_u8L("Pressure Advance"), [&vertex, &buff]() {
sprintf(buff, "%.4f", vertex.pressure_advance);
ImGuiWrapper::text(std::string(buff));
});
append_table_row(_u8L("Time"), [viewer, &vertex, &buff, vertex_id]() {
const float estimated_time = viewer->get_estimated_time_at(vertex_id);
sprintf(buff, "%s (%.3fs)", get_time_dhms(estimated_time).c_str(), vertex.times[static_cast<size_t>(viewer->get_time_mode())]);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
for (const auto& row : properties_rows)
append_table_row(row.first, row.second);
ImGui::EndTable();
}
#if ENABLE_ACTUAL_SPEED_DEBUG
bool actual_speed_exist = vertex.is_extrusion() || vertex.is_travel() || vertex.is_wipe();
//if (vertex.is_extrusion() || vertex.is_travel() || vertex.is_wipe()) { // ORCA always show button to keep properties on same place
ImGui::Spacing();
//ImGuiWrapper::text(_u8L("Actual speed profile"));
//ImGui::SameLine();
static float cached_table_wnd_width = 0.0f; // ORCA: Cache the calculated window width to avoid recalculation on every frame
static float cached_table_wnd_scale = 0.0f; // ORCA: Cache the calculated window scale to avoid recalculation on every frame
ImGui::Spacing();
static bool table_shown = false;
imgui.push_confirm_button_style();
const float table_btn_width = std::max(ImGui::CalcTextSize(_u8L("Hide").c_str()).x, ImGui::CalcTextSize(_u8L("Show").c_str()).x);
float btn_padding_x = 8.0f * m_scale;
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(btn_padding_x, 3.f * m_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(.5f, .5f));
if (imgui.button((table_shown ? _L("Hide") : _L("Show")).c_str(), ImVec2(table_btn_width + btn_padding_x * 2.f, 0.f), actual_speed_exist))
table_shown = !table_shown;
ImGui::PopStyleVar(3);
imgui.pop_confirm_button_style();
ImGui::SameLine();
ImGuiWrapper::text(_u8L("Actual speed profile").c_str()); // ORCA show label and plot on external window to make main window more compact
static bool table_shown = false;
imgui.push_confirm_button_style();
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(btn_padding_x, 3.f * m_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(.5f, .5f));
if (imgui.button((table_shown ? _L("Hide") : _L("Show")).c_str(), ImVec2(table_btn_width + btn_padding_x * 2.f, 0.f), actual_speed_exist))
table_shown = !table_shown;
ImGui::PopStyleVar(3);
imgui.pop_confirm_button_style();
ImGui::SameLine();
ImGuiWrapper::text(_u8L("Actual speed profile").c_str()); // ORCA show label and plot on external window to make main window more compact
//ImGui::Separator();
//const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.0f, 150.0f });
if (actual_speed_exist && table_shown) {
static float table_wnd_height = 0.0f;
//const ImVec2 wnd_size = ImGui::GetWindowSize();
imgui.set_next_window_pos(ImGui::GetWindowPos().x - 5.f * m_scale /*+ wnd_size.x*/, static_cast<float>(canvas_height), ImGuiCond_Always, 1.0f, 1.0f);
//ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, wnd_size.y });
imgui.begin(std::string("ToolPositionTableWnd"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(9.f, 1.f) * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered , ImGui::GetStyle().Colors[ImGuiCol_TableHeaderBg]);
const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.f, 135.f * m_scale});
if (ImGui::BeginTable("ToolPositionTable", 2, ImGuiTableFlags_Borders /*| ImGuiTableFlags_ScrollY*/)) { // ORCA showing scrollbar causes expanding window
char buff[1024];
//ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
ImGui::TableSetupColumn((_u8L("Position") + " (" + _u8L("mm") + ")").c_str());
ImGui::TableSetupColumn((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str());
ImGui::TableHeadersRow();
int counter = 0;
for (const ActualSpeedImguiWidget::Item& item : m_actual_speed_imgui_widget.data) {
const bool highlight = hover_id >= 0 && (counter == hover_id || counter == hover_id + 1);
//if (highlight && counter == hover_id)
// ImGui::SetScrollHereY();
ImGui::TableNextRow();
const ImU32 row_bg_color = ImGui::GetColorU32(item.internal ? ImVec4(0.0f, 150.f / 255.0f, 136.0f / 255.f, 0.15f) : ImVec4(0.2f, 0.2f, 0.2f, 0.25f)); // ORCA
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
ImGui::TableSetColumnIndex(0);
sprintf(buff, "%.3f", item.pos);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
ImGui::TableSetColumnIndex(1);
sprintf(buff, "%.1f", item.speed);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
++counter;
}
if (actual_speed_exist && table_shown) {
const float plot_height = 135.f * m_scale; // 135 is the height of the plot without labels
// ORCA add blank rows to keep plot in same place. row count can be 9 but mostly it shows between 3 to 5
for (int id = 7 - counter; id > 0; --id) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
imgui.text_colored(ImVec4(0.f, 0.f, 0.f, 0.f), "f");
}
if (cached_table_wnd_scale != m_scale) { // ORCA
// Catch the window width to avoid recalculation on every frame,
// but recalculate it when/if scale changes.
ImGui::EndTable();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
const float curr_table_wnd_height = ImGui::GetWindowHeight();
if (table_wnd_height != curr_table_wnd_height) {
table_wnd_height = curr_table_wnd_height;
// require extra frame to hide the table scroll bar (bug in imgui)
imgui.set_requires_extra_frame();
}
imgui.end();
const float cell_pad_x = 9.f * m_scale; // matches ImGuiStyleVar_CellPadding.x below
const float wnd_pad_x = 2.0f * style.WindowPadding.x;
const float table_border_x = 6.0f * m_scale;
// Calculate the width needed to fully show each table column header
// but not clamping to a minimum because later the window width is clamped anyway.
const float col0_w = ImGui::CalcTextSize((_u8L("Position") + " (" + _u8L("mm") + ")").c_str()).x;
const float col1_w = ImGui::CalcTextSize((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str()).x;
const float content_w = std::ceil(col0_w + col1_w + 4.0f * cell_pad_x + table_border_x + wnd_pad_x);
const float aspect_w = std::ceil(plot_height * (16.0f / 9.0f)); // 16:9 ratio for better look
// Try to obtain 16:9 ratio if possible, but keep enough width for content to make
// window looking good on different languages, especially with long words in table header
cached_table_wnd_width = std::max(content_w, aspect_w);
cached_table_wnd_scale = m_scale;
}
// ORCA: Pre-calculate the height of the table based on number of rows
// and clamp the final window height to keep at least the same height as ToolPosition window.
const float cell_pad_y = 1.f * m_scale; // ImGuiStyleVar_CellPadding.y below
const float row_height = ImGui::GetTextLineHeight() + 2.f * cell_pad_y;
const float rows = static_cast<float>(m_actual_speed_imgui_widget.data.size());
const float table_h = row_height * (1.0f + rows) + 2.f * m_scale; // border allowance
const float target_h = std::ceil(plot_height + table_h + 2.f * style.WindowPadding.y);
const float min_h = ImGui::GetWindowHeight(); // keep at least the same height as ToolPosition window
const float max_h = static_cast<float>(canvas_height) - 8.f * m_scale; // keep inside canvas with a small margin
const float final_h = std::clamp(target_h, min_h, max_h);
const bool needs_scroll = target_h > max_h;
const float table_view_h = std::max(1.0f, final_h - plot_height - 2.f * style.WindowPadding.y);
//const ImVec2 wnd_size = ImGui::GetWindowSize();
imgui.set_next_window_pos(ImGui::GetWindowPos().x - 5.f * m_scale /*+ wnd_size.x*/, static_cast<float>(canvas_height), ImGuiCond_Always, 1.0f, 1.0f);
ImGui::SetNextWindowSize(ImVec2(cached_table_wnd_width, final_h), ImGuiCond_Always);
//ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, wnd_size.y });
imgui.begin(std::string("ToolPositionTableWnd"), ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(9.f, 1.f) * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered , style.Colors[ImGuiCol_TableHeaderBg]);
const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.f, plot_height});
const ImGuiTableFlags table_flags = ImGuiTableFlags_Borders | (needs_scroll ? ImGuiTableFlags_ScrollY : 0);
if (ImGui::BeginTable("ToolPositionTable", 2, table_flags, ImVec2(0.0f, needs_scroll ? table_view_h : 0.0f))) {
char buff[1024];
if (needs_scroll)
ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
ImGui::TableSetupColumn((_u8L("Position") + " (" + _u8L("mm") + ")").c_str());
ImGui::TableSetupColumn((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str());
ImGui::TableHeadersRow();
int counter = 0;
for (const ActualSpeedImguiWidget::Item& item : m_actual_speed_imgui_widget.data) {
const bool highlight = hover_id >= 0 && (counter == hover_id || counter == hover_id + 1);
ImGui::TableNextRow();
const ImU32 row_bg_color = ImGui::GetColorU32(item.internal ? ImVec4(0.0f, 150.f / 255.0f, 136.0f / 255.f, 0.15f) : ImVec4(0.2f, 0.2f, 0.2f, 0.25f)); // ORCA
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
ImGui::TableSetColumnIndex(0);
sprintf(buff, "%.3f", item.pos);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
ImGui::TableSetColumnIndex(1);
sprintf(buff, "%.1f", item.speed);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
++counter;
}
ImGui::EndTable();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
imgui.end();
}
#endif // ENABLE_ACTUAL_SPEED_DEBUG
ImGui::Spacing();
ImGui::Separator();
ImGui::Dummy({0, ImGui::GetStyle().FramePadding.y});
ImGui::Dummy({0, style.FramePadding.y});
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
@@ -543,7 +622,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::SameLine();
if(!properties_shown)
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y); // aligns button with next group
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - style.FramePadding.y); // aligns button with next group
ImGui::BeginGroup(); // group contents to make information area more compact
@@ -551,7 +630,6 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
auto pos =vertex.position;
int max_value = std::round(std::max(std::max(pos[0], pos[1]),pos[2]));
auto precision = max_value > 9999 ? "%.1f" : max_value > 999 ? "%.2f" : "%.3f";
const float axes_width = ImGui::CalcTextSize("X 999.999sp").x;
char xBuf[32];
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::X()),"X ");
@@ -560,119 +638,30 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::Text("%s", xBuf);
char yBuf[32];
ImGui::SameLine(axes_width);
ImGui::SameLine(axes_width + axes_spacing_x);
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::Y()),"Y ");
ImGui::SameLine(0,0); // ignore item spacing
sprintf(yBuf, precision, pos[1]);
ImGui::Text("%s", yBuf);
char zBuf[32];
ImGui::SameLine(axes_width * 2);
ImGui::SameLine(2.0f * (axes_width + axes_spacing_x));
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::Z()),"Z ");
ImGui::SameLine(0,0); // ignore item spacing
sprintf(zBuf, precision, pos[2]);
ImGui::Text("%s", zBuf);
ImGui::SameLine(axes_width * 3);
ImGui::SameLine(3.0f * axes_width + 2.0f * axes_spacing_x);
ImGui::Dummy({0,0});
const bool is_extrusion = vertex.is_extrusion();
char buf[1024] = ""; char valBuf[32]; char spdBuf[128];
char spdBuf[128];
sprintf(spdBuf, "%s%.0f ", _u8L("Speed: ").c_str(), vertex.feedrate);
const float speed_width = ImGui::CalcTextSize((_u8L("Speed: ") + "9999 ").c_str()).x;
ImGuiWrapper::text(std::string(spdBuf)); // render Speed as differrent item to keep next item in same place
switch (view_type) {
case libvgcode::EViewType::FeatureType: {
if (is_extrusion && !vertex.is_option()) // ORCA show more types on FeatureType
sprintf(buf, "%s", to_string(vertex.role).c_str());
else if(vertex.is_travel() || vertex.is_option() || vertex.is_wipe())
sprintf(buf, "%s", to_string(vertex.type).c_str());
else
sprintf(buf, "%s", NA_CSTR);
break;
}
case libvgcode::EViewType::Height: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.height);
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s%s", _u8L("Height: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::Width: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.width);
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s%s", _u8L("Width: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::VolumetricFlowRate: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.volumetric_rate());
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s%s", _u8L("Flow: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::FanSpeed: {
sprintf(buf, "%s%.0f", _u8L("Fan: ").c_str(), vertex.fan_speed);
break;
}
case libvgcode::EViewType::Temperature: {
sprintf(buf, "%s%.0f", _u8L("Temperature: ").c_str(), vertex.temperature);
break;
}
case libvgcode::EViewType::LayerTimeLinear:
case libvgcode::EViewType::LayerTimeLogarithmic: {
sprintf(buf, "%s%.1f", _u8L("Layer Time: ").c_str(), vertex.layer_duration);
break;
}
case libvgcode::EViewType::Tool: {
sprintf(buf, "%s%d", _u8L("Tool: ").c_str(), vertex.extruder_id + 1);
break;
}
case libvgcode::EViewType::ColorPrint: {
sprintf(buf, "%s%d", _u8L("Color: ").c_str(), vertex.color_id + 1);
break;
}
case libvgcode::EViewType::ActualVolumetricFlowRate: {
// Don't display the actual flow, since it only gives data for the end of a segment
//if (is_extrusion)
// sprintf(valBuf, "%.2f", vertex.actual_volumetric_rate());
//else
// sprintf(valBuf, "%s", NA_CSTR);
//sprintf(buf, "%s%s", _u8L("Actual Flow: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::ActualSpeed: {
// Don't display the actual flow, since it only gives data for the end of a segment
//sprintf(buf, "%s%.1f", _u8L("Actual Speed: ").c_str(), vertex.actual_feedrate);
break;
}
case libvgcode::EViewType::Acceleration: {
sprintf(buf, "%s %s%.0f", buf, _u8L("Acceleration: ").c_str(), vertex.acceleration);
break;
}
case libvgcode::EViewType::Jerk: {
sprintf(buf, "%s %s%.1f", buf, _u8L("Jerk: ").c_str(), vertex.jerk);
break;
}
// ORCA: Add Pressure Advance visualization support
case libvgcode::EViewType::PressureAdvance: {
sprintf(buf, "%s%.4f", _u8L("PA: ").c_str(), vertex.pressure_advance);
break;
}
default:
break;
}
ImGuiWrapper::text(spdBuf); // render Speed as differrent item to keep next item in same place
if (buf[0] != '\0') { // dont render if buffer empty
if (detail_buf[0] != '\0') { // dont render if buffer empty
ImGui::SameLine(speed_width);
ImGuiWrapper::text(std::string(buf));
ImGuiWrapper::text(detail_buf);
}
ImGui::EndGroup();
imgui.end();
+2 -1
View File
@@ -33,7 +33,8 @@ class OpenGLManager;
static const float GCODE_VIEWER_SLIDER_SCALE = 0.6f;
static const float SLIDER_DEFAULT_RIGHT_MARGIN = 10.0f;
static const float SLIDER_DEFAULT_BOTTOM_MARGIN = 10.0f;
static const float SLIDER_RIGHT_MARGIN = 124.0f;
// ORCA: match right margin to the vertical slider window width to prevent overlap.
static inline const float SLIDER_RIGHT_MARGIN = IMSlider::vertical_slider_window_width();
static const float SLIDER_BOTTOM_MARGIN = 64.0f;
class GCodeViewer
{
+3
View File
@@ -8381,6 +8381,9 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
if (wxGetApp().show_3d_navigator()) {
window_height_max -= (128 * f_scale + 5);
}
else { // ORCA spacing for canvas menu
window_height_max -= (96 * f_scale + 5);
}
// ORCA simplify and correct window size and margin calculations and get values from style
ImGuiWrapper& imgui = *wxGetApp().imgui();
+35 -23
View File
@@ -278,7 +278,11 @@ class SplashScreen : public wxSplashScreen
{
public:
SplashScreen(wxPoint pos = wxDefaultPosition)
: wxSplashScreen(wxBitmap(FromDIP(wxSize(480,480),nullptr)), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 1500, nullptr, wxID_ANY, wxDefaultPosition, wxDefaultSize,
// No wxSPLASH_TIMEOUT — the splash is closed explicitly once MainFrame
// is shown. The previous 1500 ms auto-timeout closed the splash long
// before init finished, leaving the user staring at a frozen blank
// screen during the slow load_presets / new MainFrame phases.
: wxSplashScreen(wxBitmap(FromDIP(wxSize(480,480),nullptr)), wxSPLASH_CENTRE_ON_SCREEN, 0, nullptr, wxID_ANY, wxDefaultPosition, wxDefaultSize,
#ifdef __APPLE__
wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP
#else
@@ -2758,7 +2762,7 @@ bool GUI_App::on_init_inner()
//BBS use BBL splashScreen
scrn = new SplashScreen(splashscreen_pos);
wxYield();
//scrn->SetText(_L("Loading configuration")+ dots);
scrn->SetText(_L("Loading configuration") + dots);
}
BOOST_LOG_TRIVIAL(info) << "loading systen presets...";
@@ -2933,6 +2937,7 @@ bool GUI_App::on_init_inner()
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
// installation of a compatible system preset, thus nullifying the system preset substitutions.
if (scrn) { scrn->SetText(_L("Loading printer & filament profiles") + dots); wxYield(); }
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
}
catch (const std::exception& ex) {
@@ -2961,6 +2966,7 @@ bool GUI_App::on_init_inner()
}
#endif
if (scrn) { scrn->SetText(_L("Creating main window") + dots); wxYield(); }
BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame();
// hide settings tabs after first Layout
@@ -2985,8 +2991,10 @@ bool GUI_App::on_init_inner()
// ensure the selected technology is ptFFF
plater_->set_printer_technology(ptFFF);
}
else
else {
if (scrn) { scrn->SetText(_L("Loading current preset") + dots); wxYield(); }
load_current_presets();
}
if (plater_ != nullptr) {
plater_->reset_project_dirty_initial_presets();
@@ -2998,7 +3006,10 @@ bool GUI_App::on_init_inner()
#ifdef __WINDOWS__
mainframe->topbar()->SaveNormalRect();
#endif
if (scrn) { scrn->SetText(_L("Showing main window") + dots); wxYield(); }
mainframe->Show(true);
// Close the splash now that the main UI is visible.
if (scrn) { scrn->Destroy(); scrn = nullptr; }
BOOST_LOG_TRIVIAL(info) << "main frame firstly shown";
//#if BBL_HAS_FIRST_PAGE
@@ -5782,7 +5793,10 @@ void GUI_App::reload_settings()
load_pending_vendors();
preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
mainframe->update_side_preset_ui();
if (is_main_thread_active())
mainframe->update_side_preset_ui();
else
CallAfter([this] { mainframe->update_side_preset_ui(); });
}
}
@@ -6023,8 +6037,10 @@ void GUI_App::load_pending_vendors()
return;
preset_bundle->apply_vendor_config(need_add_vendors, need_add_filaments, app_config, false);
app_config->save();
if (is_main_thread_active())
app_config->save();
else
CallAfter([this] { app_config->save(); });
need_add_vendors.clear();
need_add_filaments.clear();
}
@@ -6508,36 +6524,29 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
cancelFn = [this, dlg]() {
return is_closing() || dlg->WasCanceled();
};
finishFn = [this, userid = m_agent->get_user_id(), dlg, t = std::weak_ptr<int>(m_user_sync_token)](bool ok) {
CallAfter([=]{
dlg->Destroy();
if (ok && m_agent && t.lock() == m_user_sync_token && userid == m_agent->get_user_id()) reload_settings();
});
finishFn = [this, dlg](bool) {
CallAfter([=]{ dlg->Destroy(); });
};
}
else {
finishFn = [this, userid = m_agent->get_user_id(), t = std::weak_ptr<int>(m_user_sync_token)](bool ok) {
CallAfter([=] {
if (ok && m_agent && t.lock() == m_user_sync_token && userid == m_agent->get_user_id()) reload_settings();
});
};
finishFn = [](bool) {}; // reload_settings() is now triggered from the background thread
cancelFn = [this]() {
return is_closing();
};
}
// Do a one-time scan for files that may be pending deletion (e.g., was deleted while not connected to internet)
// Scan for orphaned .info files on startup and add them to deletion queue
scan_orphaned_info_files();
process_delete_presets();
Bind(EVT_UPDATE_PRESET_BUNDLE,&GUI_App::update_single_bundle,this);
m_sync_update_thread = Slic3r::create_thread(
[this, progressFn, cancelFn, finishFn, t = std::weak_ptr<int>(m_user_sync_token)] {
if (!m_agent) return;
// One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs.
scan_orphaned_info_files();
process_delete_presets();
// get setting list, update setting list
std::string version = preset_bundle->get_vendor_profile_version(PresetBundle::ORCA_DEFAULT_BUNDLE).to_string();
if(!m_agent) return;
// run check_and_fix_user_presets_syncinfo once before syncing to make sure all presets have correct sync_info
// So that we can sync presets that are migrated from old version or users manually put preset files in preset folder
@@ -6562,9 +6571,12 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
}
}, progressFn, cancelFn);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " get_setting_list2 ret = " << ret << " m_is_closing = " << m_is_closing;
finishFn(ret == 0);
if (ret == 0 && m_agent && !t.expired())
reload_settings();
// For orca specific syncing
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
int tick_tock = -1, sync_count = 0; // tick_tock = -1 to immediately run sync the frist time this thread runs
+45 -27
View File
@@ -18,7 +18,8 @@ inline double miscalculation() { return scale_(scale_(1)); }
static const float LEFT_MARGIN = 13.0f + 100.0f; // avoid thumbnail toolbar
static const float HORIZONTAL_SLIDER_WINDOW_HEIGHT = 64.0f;
static const float VERTICAL_SLIDER_WINDOW_WIDTH = 160.0f;
// ORCA: widen slider window to account for one-layer label box left shift.
static const float VERTICAL_SLIDER_WINDOW_WIDTH = 164.0f;
static const float GROOVE_WIDTH = 12.0f;
static const ImVec2 ONE_LAYER_MARGIN = ImVec2(20.0f, 20.0f);
static const ImVec2 ONE_LAYER_BUTTON_SIZE = ImVec2(56.0f, 56.0f);
@@ -33,6 +34,12 @@ static const ImU32 BRAND_COLOR = IM_COL32(0, 150, 136, 255);
static int m_tick_value = -1;
static ImVec4 m_tick_rect;
// ORCA: keep the slider window width in one place for layout alignment.
float IMSlider::vertical_slider_window_width()
{
return VERTICAL_SLIDER_WINDOW_WIDTH;
}
bool equivalent_areas(const double& bottom_area, const double& top_area)
{
return fabs(bottom_area - top_area) <= miscalculation();
@@ -892,6 +899,11 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
const ImRect groove = ImRect(groove_start, groove_start + groove_size);
const ImRect bg_rect = ImRect(groove.Min - ImVec2(6.0f, 6.0f) * m_scale, groove.Max + ImVec2(6.0f, 6.0f) * m_scale);
const float mid_x = groove.GetCenter().x;
// ORCA: tune label box width to fit the slider window without overlapping the groove.
const float label_extra_padding = 10.0f * m_scale;
const float one_layer_extra_padding = 6.0f * m_scale;
const float max_label_width = std::max(0.0f,
groove.Min.x - draw_region.Min.x - triangle_offsets[2].x - text_padding.x * 2.0f - label_extra_padding);
// set mouse active region.
const ImRect active_region = ImRect(ImVec2(draw_region.Min.x + 35.0f * m_scale, draw_region.Min.y), draw_region.Max);
@@ -999,30 +1011,33 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
window->DrawList->AddLine(lower_handle_center + ImVec2(0.0f, -0.5f * line_length), lower_handle_center + ImVec2(0.0f, 0.5f * line_length), white_bg, line_width);
}
// draw higher label
auto text_utf8 = into_u8(higher_label);
text_content_size = ImGui::CalcTextSize(text_utf8.c_str());
text_size = text_content_size + text_padding * 2;
ImVec2 text_start = ImVec2(higher_handle.Min.x - text_size.x - triangle_offsets[2].x, higher_handle_center.y - text_size.y);
ImRect text_rect(text_start, text_start + text_size);
ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding);
ImVec2 pos_1 = text_rect.Max - triangle_offsets[0];
ImVec2 pos_2 = pos_1 - triangle_offsets[1];
ImVec2 pos_3 = pos_1 + triangle_offsets[2];
window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg);
ImGui::RenderText(text_start + text_padding, higher_label.c_str());
// draw lower label
text_utf8 = into_u8(lower_label);
text_content_size = ImGui::CalcTextSize(text_utf8.c_str());
text_size = text_content_size + text_padding * 2;
text_start = ImVec2(lower_handle.Min.x - text_size.x - triangle_offsets[2].x, lower_handle_center.y);
text_rect = ImRect(text_start, text_start + text_size);
ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding);
pos_1 = ImVec2(text_rect.Max.x, text_rect.Min.y) - triangle_offsets[0];
pos_2 = pos_1 + triangle_offsets[1];
pos_3 = pos_1 + triangle_offsets[2];
window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg);
ImGui::RenderText(text_start + text_padding, lower_label.c_str());
// ORCA: render fixed-width label boxes
// draw higher label
auto text_utf8 = into_u8(higher_label);
text_content_size = ImGui::CalcTextSize(text_utf8.c_str());
text_size = ImVec2(max_label_width, text_content_size.y) + text_padding * 2;
ImVec2 text_start = ImVec2(higher_handle.Min.x - text_size.x - triangle_offsets[2].x, higher_handle_center.y - text_size.y);
ImRect text_rect(text_start, text_start + text_size);
ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding);
ImVec2 pos_1 = text_rect.Max - triangle_offsets[0];
ImVec2 pos_2 = pos_1 - triangle_offsets[1];
ImVec2 pos_3 = pos_1 + triangle_offsets[2];
window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg);
ImGui::RenderText(text_start + ImVec2((text_size.x - text_content_size.x) * 0.5f,
(text_size.y - text_content_size.y) * 0.5f), higher_label.c_str());
// draw lower label
text_utf8 = into_u8(lower_label);
text_content_size = ImGui::CalcTextSize(text_utf8.c_str());
text_size = ImVec2(max_label_width, text_content_size.y) + text_padding * 2;
text_start = ImVec2(lower_handle.Min.x - text_size.x - triangle_offsets[2].x, lower_handle_center.y);
text_rect = ImRect(text_start, text_start + text_size);
ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding);
pos_1 = ImVec2(text_rect.Max.x, text_rect.Min.y) - triangle_offsets[0];
pos_2 = pos_1 + triangle_offsets[1];
pos_3 = pos_1 + triangle_offsets[2];
window->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, white_bg);
ImGui::RenderText(text_start + ImVec2((text_size.x - text_content_size.x) * 0.5f,
(text_size.y - text_content_size.y) * 0.5f), lower_label.c_str());
// draw mouse position
if (hovered) {
@@ -1062,11 +1077,14 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
// draw label
auto text_utf8 = into_u8(higher_label);
text_content_size = ImGui::CalcTextSize(text_utf8.c_str());
text_size = text_content_size + text_padding * 2;
// ORCA: slightly narrower label box in one-layer mode to avoid left shift.
text_size = ImVec2(std::max(0.0f, max_label_width - label_extra_padding - one_layer_extra_padding),
text_content_size.y) + text_padding * 2;
ImVec2 text_start = ImVec2(one_handle.Min.x - text_size.x, handle_center.y - 0.5 * text_size.y);
ImRect text_rect = ImRect(text_start, text_start + text_size);
ImGui::RenderFrame(text_rect.Min, text_rect.Max, white_bg, false, text_frame_rounding);
ImGui::RenderText(text_start + text_padding, higher_label.c_str());
ImGui::RenderText(text_start + ImVec2((text_size.x - text_content_size.x) * 0.5f,
(text_size.y - text_content_size.y) * 0.5f), higher_label.c_str());
// draw mouse position
if (hovered) {
+2
View File
@@ -128,6 +128,8 @@ public:
float m_scale = 1.0;
void set_scale(float scale = 1.0);
// ORCA: expose vertical slider width for right-margin alignment in the preview UI.
static float vertical_slider_window_width();
void on_change_color_mode(bool is_dark);
void set_menu_enable(bool enable = true) { m_menu_enable = enable; }
+29 -35
View File
@@ -87,45 +87,39 @@ namespace instance_check_internal
#ifdef _WIN32
static HWND l_bambu_studio_hwnd;
static HWND orca_slicer_hwnd;
static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam)
{
//checks for other instances of prusaslicer, if found brings it to front and return false to stop enumeration and quit this instance
//search is done by classname(wxWindowNR is wxwidgets thing, so probably not unique) and name in window upper panel
//other option would be do a mutex and check for its existence
//BOOST_LOG_TRIVIAL(error) << "ewp: version: " << l_version_wstring;
TCHAR wndText[1000];
TCHAR className[1000];
int err;
err = GetClassName(hwnd, className, 1000);
if (err == 0)
// ORCA: Find the already-running instance by its window properties
TCHAR className[256]; // class names are limited to 255 characters, see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
if (GetClassName(hwnd, className, 256) == 0)
return true;
err = GetWindowText(hwnd, wndText, 1000);
if (err == 0)
if (std::wstring(className) != L"wxWindowNR")
return true;
std::wstring classNameString(className);
std::wstring wndTextString(wndText);
if (wndTextString.find(L"OrcaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") {
//check if other instances has same instance hash
//if not it is not same version(binary) as this version
HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor");
uint64_t other_instance_hash = PtrToUint(handle);
uint64_t other_instance_hash_major;
uint64_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int();
handle = GetProp(hwnd, L"Instance_Hash_Major");
other_instance_hash_major = PtrToUint(handle);
other_instance_hash_major = other_instance_hash_major << 32;
other_instance_hash += other_instance_hash_major;
if(my_instance_hash == other_instance_hash)
{
BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance";
l_bambu_studio_hwnd = hwnd;
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
SetForegroundWindow(hwnd);
return false;
}
BOOST_LOG_TRIVIAL(debug) << "win enum - found wrong instance";
// Check if the candidate window has the same instance hash. If the
// properties are missing, it is not an OrcaSlicer main window.
HANDLE handle_minor = GetProp(hwnd, L"Instance_Hash_Minor");
HANDLE handle_major = GetProp(hwnd, L"Instance_Hash_Major");
if (handle_minor == nullptr || handle_major == nullptr)
return true;
uint64_t other_instance_hash = PtrToUint(handle_minor);
uint64_t other_instance_hash_major = PtrToUint(handle_major);
other_instance_hash_major = other_instance_hash_major << 32;
other_instance_hash += other_instance_hash_major;
uint64_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int();
if (my_instance_hash == other_instance_hash) {
BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance";
orca_slicer_hwnd = hwnd;
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
SetForegroundWindow(hwnd);
return false;
}
BOOST_LOG_TRIVIAL(debug) << "win enum - found wrong instance";
return true;
}
static bool send_message(const std::string& message, const std::string &version)
@@ -145,7 +139,7 @@ namespace instance_check_internal
data_to_send.dwData = 1;
data_to_send.cbData = sizeof(TCHAR) * (wcslen(*command_line_args.get()) + 1);
data_to_send.lpData = *command_line_args.get();
SendMessage(l_bambu_studio_hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send);
SendMessage(orca_slicer_hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send);
return true;
}
return false;
+18 -3
View File
@@ -215,8 +215,13 @@ void PrintJob::process(Ctl &ctl)
std::string devIP = m_dev_ip;
std::string accessCode = m_access_code;
std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
std::unique_ptr<FileTransferTunnel> tunnel = std::make_unique<FileTransferTunnel>(module(), url);
emmc_ok = tunnel->sync_start_connect();
try {
std::unique_ptr<FileTransferTunnel> tunnel = std::make_unique<FileTransferTunnel>(module(), url);
emmc_ok = tunnel->sync_start_connect();
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(warning) << "eMMC tunnel unavailable, falling back to FTP: " << e.what();
emmc_ok = false;
}
}
{
params.dev_id = m_dev_id;
@@ -270,7 +275,17 @@ void PrintJob::process(Ctl &ctl)
params.auto_flow_cali = this->auto_flow_cali;
params.auto_offset_cali = this->auto_offset_cali;
params.task_ext_change_assist = this->task_ext_change_assist;
params.try_emmc_print = this->could_emmc_print;
// Allow disabling the eMMC print path via AppConfig. Plugin 02.03.00.62's
// eMMC tunnel code hangs indefinitely at the upload phase with some
// printers (e.g., Bambu H2D), so we default to disabled. Users with
// working eMMC support can opt-in by setting disable_emmc_print = 0.
bool disable_emmc = true;
if (wxGetApp().app_config) {
auto v = wxGetApp().app_config->get("disable_emmc_print");
if (v == "0" || v == "false")
disable_emmc = false;
}
params.try_emmc_print = this->could_emmc_print && !disable_emmc;
if (m_print_type == "from_sdcard_view") {
params.dst_file = m_dst_path;
+2 -4
View File
@@ -46,6 +46,7 @@
#include "Widgets/ProgressDialog.hpp"
#include "BindDialog.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/PrintHost.hpp"
#include <fstream>
#include <string_view>
@@ -4186,15 +4187,12 @@ void MainFrame::load_printer_url()
return;
auto cfg = preset_bundle.printers.get_edited_preset().config;
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect))
apikey = cfg.opt_string("printhost_apikey");
if (!url.empty()) {
if (!url.Lower().starts_with("http"))
url = wxString::Format("http://%s", url);
load_printer_url(url, apikey);
}
}
+17 -6
View File
@@ -58,7 +58,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
main_sizer->Add(topsizer, 1, wxEXPAND);
m_dsa_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->Add(0, 0, 0, wxLEFT, FromDIP(120));
btn_sizer->Add(0, 0, 0, wxLEFT, FromDIP(LOGO_SPACING + 64 + LOGO_GAP));
btn_sizer->Add(m_dsa_sizer, 0, wxEXPAND);
btn_sizer->AddStretchSpacer();
main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND | wxTOP, FromDIP(10));
@@ -76,7 +76,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
void MsgDialog::show_dsa_button(wxString const &title)
{
m_checkbox_dsa = new CheckBox(this);
m_dsa_sizer->Add(m_checkbox_dsa, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
m_dsa_sizer->Add(m_checkbox_dsa, 0, wxALIGN_CENTER);
m_checkbox_dsa->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
auto event = wxCommandEvent(EVT_CHECKBOX_CHANGE);
event.SetInt(m_checkbox_dsa->GetValue()?1:0);
@@ -86,7 +86,17 @@ void MsgDialog::show_dsa_button(wxString const &title)
});
auto m_text_dsa = new wxStaticText(this, wxID_ANY, title.IsEmpty() ? _L("Don't show again") : title, wxDefaultPosition, wxDefaultSize, 0);
auto on_toggle = [this]() {
m_checkbox_dsa->SetValue(!m_checkbox_dsa->GetValue());
wxCommandEvent ev(wxEVT_TOGGLEBUTTON, m_checkbox_dsa->GetId());
ev.SetEventObject(m_checkbox_dsa);
m_checkbox_dsa->GetEventHandler()->ProcessEvent(ev);
};
m_text_dsa->Bind(wxEVT_LEFT_DOWN, [on_toggle](wxMouseEvent& e) {if(!e.LeftDClick()) on_toggle();});
m_text_dsa->Bind(wxEVT_LEFT_DCLICK, [on_toggle](wxMouseEvent& e) {on_toggle();});
m_dsa_sizer->Add(m_text_dsa, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
m_dsa_sizer->AddSpacer(FromDIP(10)); // spacing after checkbox
m_text_dsa->SetFont(::Label::Body_13);
m_text_dsa->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
btn_sizer->Layout();
@@ -169,7 +179,7 @@ Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, con
if (set_focus)
btn->SetFocus();
btn_sizer->Add(btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::ChoiceButtonGap()));
btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::ChoiceButtonGap()));
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { EndModal(btn_id); });
MsgButton *mb = new MsgButton;
@@ -289,7 +299,8 @@ static void add_msg_content(wxWindow *parent,
}
else {
wxClientDC dc(parent);
wxSize msg_sz = dc.GetMultiLineTextExtent(msg);
dc.SetFont(font); // ORCA without this it calculates bigger size
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
// Extra line breaks in message dialog
@@ -355,7 +366,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monosp
add_msg_content(this, content_sizer, msg, monospaced_font);
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84));
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
SetMaxSize(MSG_DLG_MAX_SIZE);
@@ -563,7 +574,7 @@ Newer3mfVersionDialog::Newer3mfVersionDialog(wxWindow *parent, const Semver *fil
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
wxBoxSizer * content_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticBitmap *info_bitmap = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("info", nullptr, 60), wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70)), 0);
wxStaticBitmap *info_bitmap = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("info", nullptr, 64), wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70)), 0);
wxBoxSizer * msg_sizer = get_msg_sizer();
content_sizer->Add(info_bitmap, 0, wxEXPAND | wxALL, FromDIP(5));
content_sizer->Add(msg_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
+1 -1
View File
@@ -71,7 +71,7 @@ struct MsgDialog : DPIDialog
protected:
enum {
BORDER = 20,
LOGO_SPACING = 35,
LOGO_SPACING = 25,
LOGO_GAP = 20,
CONTENT_WIDTH = 242,
CONTENT_MAX_HEIGHT = 60,//TO
+23 -14
View File
@@ -239,6 +239,8 @@ void NotificationManager::PopNotification::use_bbl_theme()
m_WindowBkgColor = m_is_dark ? ImVec4(45 / 255.f, 45 / 255.f, 49 / 255.f, 1.f) : ImVec4(1, 1, 1, 1);
m_TextColor = m_is_dark ? ImVec4(224 / 255.f, 224 / 255.f, 224 / 255.f, 1.f) : ImVec4(.2f, .2f, .2f, 1.0f);
m_HyperTextColor = m_is_dark ? ImVec4(0, 0.588, 0.533, 1) : ImVec4(0, 0.588, 0.533, 1);
m_HyperTextColorHover = m_is_dark ? ImVec4(38.f / 255.f, 166.f / 255.f, 154.f / 255.f, 1) : ImVec4(0.f, 129.f / 255.f, 114.f / 255.f, 1); //#26A69A / #008172;
m_is_dark ? push_style_color(ImGuiCol_Border, {62 / 255.f, 62 / 255.f, 69 / 255.f, 1.f}, true, m_current_fade_opacity) : push_style_color(ImGuiCol_Border, m_CurrentColor, true, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, m_WindowBkgColor, true, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, m_TextColor, true, m_current_fade_opacity);
@@ -759,11 +761,17 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
HyperColor = ImVec4(135.f / 255.f, 43 / 255.f, 43 / 255.f, 1);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
{
HyperColor.y += 0.1f;
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel || m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
HyperColor.x += 0.2f;
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel){
HyperColor.y += 0.1f;
HyperColor.x += 0.2f;
}
else if(m_data.level == NotificationLevel::ErrorNotificationLevel){
HyperColor.y += 0.1f;
}
else {
HyperColor = m_HyperTextColorHover;
}
}
//text
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
@@ -2255,8 +2263,9 @@ void NotificationManager::push_import_finished_notification(const std::string& p
void NotificationManager::SharedProfilesNotification::init()
{
PopNotification::init();
// Add one extra line for the hyperlink row ("Browse shared profiles" + "Don't show again")
m_lines_count++;
// Add two extra lines for the hyperlink row ("Browse shared profiles" + "Don't show again")
// and 1 more additional line for adding spacing between them to make it easier to click
m_lines_count = m_lines_count + 2; // ORCA
}
void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper& imgui,
@@ -2283,18 +2292,18 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
}
// Render "Browse shared profiles" hyperlink on the next line
float hyper_y = starting_y + m_endlines.size() * shift_y;
float hyper_y = starting_y + m_endlines.size() * shift_y - m_line_height / 2.f;
render_hypertext(imgui, x_offset, hyper_y, m_hypertext);
// Render "Don't show again" hyperlink after the browse link
{
float dont_show_x = x_offset + ImGui::CalcTextSize((m_hypertext + " ").c_str()).x;
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height / 2.f;
std::string dont_show_text = _u8L("Don't show again");
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
// Invisible button
ImGui::SetCursorPosX(dont_show_x - 4);
ImGui::SetCursorPosY(hyper_y - 5);
ImGui::SetCursorPosX(x_offset); // ORCA render on new line to prevent long translations from being cut off
ImGui::SetCursorPosY(dont_show_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
@@ -2308,12 +2317,12 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
// Hover color
ImVec4 color = m_HyperTextColor;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
color.y += 0.1f;
color = m_HyperTextColorHover;
// Text
push_style_color(ImGuiCol_Text, color, m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::SetCursorPosX(dont_show_x);
ImGui::SetCursorPosY(hyper_y);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(dont_show_y);
imgui.text(dont_show_text.c_str());
ImGui::PopStyleColor();
@@ -2354,7 +2363,7 @@ void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrap
// Hover color
ImVec4 HyperColor = m_HyperTextColor;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
HyperColor.y += 0.1f;
HyperColor = m_HyperTextColorHover;
// Text
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
+1
View File
@@ -543,6 +543,7 @@ private:
ImVec4 m_TextColor;
ImVec4 m_HyperTextColor;
ImVec4 m_HyperTextColorHover;
ImVec4 m_ErrorColor;
ImVec4 m_WarnColor;
+24 -67
View File
@@ -83,6 +83,7 @@
#include "GUI_Utils.hpp"
#include "GUI_Factories.hpp"
#include "wxExtensions.hpp"
#include "../Utils/PrintHost.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "3DScene.hpp"
@@ -2385,7 +2386,7 @@ void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int filame
(*combo)->clr_picker->SetLabel(wxString::Format("%d", filament_idx + 1));
combo_and_btn_sizer->Add((*combo)->clr_picker, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(SidebarProps::ElementSpacing()) - FromDIP(2)); // ElementSpacing - 2 (from combo box))
combo_and_btn_sizer->Add(*combo, 1, wxALL | wxEXPAND, FromDIP(2))->SetMinSize({-1, FromDIP(30)});
combo_and_btn_sizer->Add(*combo, 1, wxALL | wxEXPAND, FromDIP(2))->SetMinSize({-1, 30 * wxGetApp().em_unit() / 10}); // ORCA ensure height matches with PlaterPresetComboBox
/* BBS hide del_btn
ScalableButton* del_btn = new ScalableButton(p->m_panel_filament_content, wxID_ANY, "delete_filament");
@@ -2486,13 +2487,11 @@ void Sidebar::update_all_preset_comboboxes()
p->m_bpButton_ams_filament->Hide();
auto print_btn_type = MainFrame::PrintSelectType::eExportGcode;
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
if(url.empty())
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
else {
if (!url.Lower().starts_with("http"))
url = wxString::Format("http://%s", url);
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
apikey = cfg.opt_string("printhost_apikey");
@@ -14157,7 +14156,6 @@ public:
wxPanel * m_top_line;
wxStaticText *m_fname_title;
wxStaticText *m_fname_f;
wxStaticText *m_fname_s;
StaticBox * m_panel_select;
void on_select_ok(wxCommandEvent &event);
@@ -14167,7 +14165,6 @@ public:
void set_action(int index) { m_action = index; }
wxBoxSizer *create_remember_checkbox(wxString title, wxWindow* parent, wxString tooltip);
wxBoxSizer *create_item_radiobox(wxString title, wxWindow *parent, int select_id, int groupid);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
@@ -14185,10 +14182,6 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
// def setting
SetBackgroundColour(m_def_color);
// icon
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
m_top_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
@@ -14196,37 +14189,23 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
m_sizer_main->Add(m_top_line, 0, wxEXPAND, 0);
m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, 20);
wxBoxSizer *m_sizer_name = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *m_sizer_fline = new wxBoxSizer(wxHORIZONTAL);
m_sizer_main->AddSpacer(FromDIP(15));
// ORCA use file name on new line to create room for longer names
m_fname_title = new wxStaticText(this, wxID_ANY, _L("Please select an action"), wxDefaultPosition, wxDefaultSize, 0);
m_fname_title->Wrap(-1);
m_fname_title->SetFont(::Label::Body_14);
m_fname_title->SetForegroundColour(wxColour(107, 107, 107));
m_fname_title->SetBackgroundColour(wxColour(255, 255, 255));
m_fname_title->SetForegroundColour(wxColour("#363636"));
m_sizer_fline->Add(m_fname_title, 0, wxALL, 0);
m_sizer_fline->Add(0, 0, 0, wxEXPAND | wxLEFT, 5);
m_fname_f = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
m_fname_f = new wxStaticText(this, wxID_ANY, filename);
m_fname_f->SetFont(::Label::Head_14);
m_fname_f->Wrap(-1);
m_fname_f->SetForegroundColour(wxColour(38, 46, 48));
m_fname_f->SetMaxSize(wxSize(FromDIP(300),-1));
m_fname_f->Wrap(FromDIP(300));
m_fname_f->SetForegroundColour(wxColour("#363636"));
m_sizer_fline->Add(m_fname_f, 1, wxALL, 0);
m_sizer_name->Add(m_sizer_fline, 1, wxEXPAND, 0);
m_fname_s = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
m_fname_s->SetFont(::Label::Head_14);
m_fname_s->Wrap(-1);
m_fname_s->SetForegroundColour(wxColour(38, 46, 48));
m_sizer_name->Add(m_fname_s, 1, wxALL, 0);
m_sizer_main->Add(m_sizer_name, 1, wxEXPAND | wxLEFT | wxRIGHT, 20);
m_sizer_main->Add(m_fname_title, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_sizer_main->AddSpacer(FromDIP(10));
m_sizer_main->Add(m_fname_f , 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_sizer_main->AddSpacer(FromDIP(10));
auto radio_group = new RadioGroup(this, {
_L("Open as project"), // 0
@@ -14238,9 +14217,9 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
set_action(radio_group->GetSelection() + 1);
});
m_sizer_main->Add(radio_group, 0, wxEXPAND | wxLEFT | wxRIGHT, 20);
m_sizer_main->Add(radio_group, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(20));
m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, 10);
m_sizer_main->AddSpacer(FromDIP(10));
// wxBoxSizer *m_sizer_bottom = new wxBoxSizer(wxHORIZONTAL);
// Orca: hide the "Don't show again" checkbox, people keeps accidentally checked this then forgot
@@ -14264,29 +14243,6 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
Fit();
Centre(wxBOTH);
auto limit_width = m_fname_f->GetSize().GetWidth() - 2;
auto current_width = 0;
auto cut_index = 0;
auto fstring = wxString("");
auto bstring = wxString("");
//auto file_name = from_u8(filename.c_str());
auto file_name = wxString(filename);
for (int x = 0; x < file_name.length(); x++) {
current_width += m_fname_s->GetTextExtent(file_name[x]).GetWidth();
cut_index = x;
if (current_width > limit_width) {
bstring += file_name[x];
} else {
fstring += file_name[x];
}
}
m_fname_f->SetLabel(fstring);
m_fname_s->SetLabel(bstring);
wxGetApp().UpdateDlgDarkUI(this);
}
@@ -15252,7 +15208,7 @@ Preset *get_printer_preset(const MachineObject *obj)
return nullptr;
Preset *printer_preset = nullptr;
float machine_nozzle_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
for (auto printer_it = preset_bundle->printers.begin(); printer_it != preset_bundle->printers.end(); printer_it++) {
// only use system printer preset
@@ -15265,7 +15221,8 @@ Preset *get_printer_preset(const MachineObject *obj)
std::string model_id = printer_it->get_current_printer_type(preset_bundle);
std::string printer_type = obj->get_show_printer_type();
if (model_id.compare(printer_type) == 0 && printer_nozzle_vals && abs(printer_nozzle_vals->get_at(0) - machine_nozzle_diameter) < 1e-3) {
bool nozzle_diameter_matches_or_unknown = printer_nozzle_vals && obj->GetExtderSystem()->NozzleDiameterMatchesOrUnknown(0, printer_nozzle_vals->get_at(0));
if (model_id.compare(printer_type) == 0 && nozzle_diameter_matches_or_unknown) {
printer_preset = &(*printer_it);
}
}
@@ -15281,12 +15238,12 @@ bool Plater::check_printer_initialized(MachineObject *obj, bool only_warning, bo
const auto& extruders = obj->GetExtderSystem()->GetExtruders();
for (const DevExtder& extruder : extruders) {
if (obj->is_multi_extruders()) {
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) {
has_been_initialized = false;
break;
}
// Skip check if nozzle type is unknown
if (extruder.GetNozzleType() == NozzleType::ntUndefine) {
continue;
}
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) {
has_been_initialized = false;
break;
+4
View File
@@ -69,6 +69,7 @@ std::tuple<wxBoxSizer*, ComboBox*> PreferencesDialog::create_item_combobox_base(
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
combobox->GetDropDown().SetUseContentWidth(true);
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) {
@@ -190,6 +191,7 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
combobox->GetDropDown().SetUseContentWidth(true);
auto language = app_config->get(param);
m_current_language_selected = -1;
std::vector<wxString>::iterator iter;
@@ -354,6 +356,7 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxStr
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
combobox->GetDropDown().SetUseContentWidth(true);
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
std::vector<wxString>::iterator iter;
@@ -433,6 +436,7 @@ wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxS
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
combobox->GetDropDown().SetUseContentWidth(true);
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); }
+3
View File
@@ -1241,6 +1241,9 @@ void PlaterPresetComboBox::update()
// Remove the old preset name if exists, and add the new one with the same name but with modified suffix if needed.
if (system_presets.erase(alternate_name))
system_presets.emplace(name, bmp);
preset_aliases.erase(alternate_name); // ORCA: do this to aliases too
preset_aliases[name] = name.utf8_string();
}
} else {
system_presets.emplace(name, bmp);
+1 -1
View File
@@ -643,7 +643,7 @@ void ElegooPrintHostSendDialog::init() {
auto preset_bundle = wxGetApp().preset_bundle;
auto model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (!boost::starts_with(model_id, "Elegoo-C")) {
if (model_id != "Elegoo-CC" && model_id != "Elegoo-C") {
PrintHostSendDialog::init();
return;
}
+46 -10
View File
@@ -1,16 +1,17 @@
#include "PrinterWebView.hpp"
#include "I18N.hpp"
#include "PrinterWebViewHandler.hpp"
#include "slic3r/GUI/PrinterWebView.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "libslic3r_version.h"
#include <boost/filesystem/path.hpp>
#include <wx/sizer.h>
#include <wx/string.h>
#include <wx/toolbar.h>
#include <wx/textdlg.h>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <wx/webview.h>
@@ -19,13 +20,17 @@
#include <webkit2/webkit2.h>
#endif
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
PrinterWebView::PrinterWebView(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_browser(nullptr)
, m_zoomFactor(100)
, m_apikey()
, m_apikey_sent(false)
, m_url_deferred()
, m_handler(std::make_unique<PrinterWebViewHandler>(*this))
{
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
@@ -47,6 +52,8 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
SetSizer(topsizer);
@@ -64,9 +71,6 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
}
*/
//Zoom
m_zoomFactor = 100;
//Connect the idle events
Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this);
@@ -76,11 +80,18 @@ PrinterWebView::~PrinterWebView()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start";
SetEvtHandlerEnabled(false);
m_handler.reset();
// Destroy the webview
if(m_browser){
m_browser->Destroy();
m_browser = nullptr;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
}
void PrinterWebView::load_url(wxString& url, wxString apikey)
{
// this->Show();
@@ -89,9 +100,11 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
return;
m_apikey = apikey;
m_apikey_sent = false;
m_handler = create_printer_webview_handler(*this);
if (this->IsShown()) {
m_url_deferred.clear();
//ORCA: m_url_deferred will be cleared on load success
//m_url_deferred.clear();
m_browser->LoadURL(url);
} else {
m_url_deferred = url;
@@ -104,7 +117,8 @@ bool PrinterWebView::Show(bool show)
{
if (show && !m_url_deferred.empty()) {
m_browser->LoadURL(m_url_deferred);
m_url_deferred.clear();
//ORCA: m_url_deferred will be cleared on load success
//m_url_deferred.clear();
}
return wxPanel::Show(show);
}
@@ -188,12 +202,34 @@ void PrinterWebView::OnError(wxWebViewEvent &evt)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": error loading page %1% %2% %3% %4%") %evt.GetURL() %evt.GetTarget() %e %evt.GetString();
}
void PrinterWebView::OnLoaded(wxWebViewEvent &evt)
void PrinterWebView::OnLoaded(wxWebViewEvent& evt)
{
if (evt.GetURL().IsEmpty())
return;
//ORCA: url loaded successfully, safe to clear
m_url_deferred.clear();
SendAPIKey();
if (m_handler != nullptr) {
m_handler->on_loaded(evt);
return;
}
}
void PrinterWebView::OnNewWindow(wxWebViewEvent& evt)
{
const wxString url = evt.GetURL();
if (!url.empty())
wxLaunchDefaultBrowser(url);
evt.Veto();
}
void PrinterWebView::OnScriptMessage(wxWebViewEvent& evt)
{
if (m_handler != nullptr)
m_handler->on_script_message(evt);
}
} // GUI
} // Slic3r
+8 -1
View File
@@ -25,11 +25,14 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
#include <memory>
namespace Slic3r {
namespace GUI {
class PrinterWebViewHandler;
class PrinterWebView : public wxPanel {
public:
@@ -41,20 +44,24 @@ public:
void OnClose(wxCloseEvent& evt);
void OnError(wxWebViewEvent& evt);
void OnLoaded(wxWebViewEvent& evt);
void OnNewWindow(wxWebViewEvent& evt);
void OnScriptMessage(wxWebViewEvent& evt);
void reload();
void update_mode();
bool Show(bool show = true) override;
private:
friend class PrinterWebViewHandler;
void SendAPIKey();
wxWebView* m_browser;
long m_zoomFactor;
wxString m_apikey;
bool m_apikey_sent;
wxString m_url_deferred;
std::unique_ptr<PrinterWebViewHandler> m_handler;
// DECLARE_EVENT_TABLE()
};
+339
View File
@@ -0,0 +1,339 @@
#include "PrinterWebViewHandler.hpp"
#include "I18N.hpp"
#include "PrinterWebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/WebView.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#include "libslic3r/Preset.hpp"
#include <nlohmann/json.hpp>
#include <atomic>
#include <boost/filesystem/path.hpp>
#include <thread>
#include <wx/filedlg.h>
#include <wx/string.h>
using json = nlohmann::json;
namespace Slic3r {
namespace GUI {
PrinterWebViewHandler::PrinterWebViewHandler(PrinterWebView& owner)
: m_owner(owner)
{
}
PrinterWebViewHandler::~PrinterWebViewHandler() = default;
void PrinterWebViewHandler::on_loaded(wxWebViewEvent &evt)
{
}
void PrinterWebViewHandler::on_script_message(wxWebViewEvent &evt)
{
}
PrinterWebView& PrinterWebViewHandler::owner() const
{
return m_owner;
}
wxWebView* PrinterWebViewHandler::browser() const
{
return m_owner.m_browser;
}
namespace {
DynamicPrintConfig* get_active_printer_config()
{
if (wxGetApp().preset_bundle == nullptr)
return nullptr;
return &wxGetApp().preset_bundle->printers.get_edited_preset().config;
}
std::string json_string(const json& node, const char* key)
{
auto it = node.find(key);
return (it != node.end() && it->is_string()) ? it->get<std::string>() : std::string();
}
std::string dump_json(const json& node)
{
return node.dump(-1, ' ', false, json::error_handler_t::replace);
}
boost::filesystem::path path_from_utf8(const std::string& utf8_path)
{
#ifdef _WIN32
const wxString wide_path = wxString::FromUTF8(utf8_path.c_str());
return boost::filesystem::path(wide_path.ToStdWstring());
#else
return boost::filesystem::path(utf8_path);
#endif
}
std::string filename_to_utf8(const boost::filesystem::path& path)
{
#ifdef _WIN32
const wxString wx_filename(path.filename().c_str());
const wxScopedCharBuffer utf8 = wx_filename.ToUTF8();
return utf8.data() != nullptr ? std::string(utf8.data()) : std::string();
#else
return path.filename().string();
#endif
}
class ElegooPrinterWebViewHandler final : public PrinterWebViewHandler {
public:
explicit ElegooPrinterWebViewHandler(PrinterWebView& owner)
: PrinterWebViewHandler(owner)
{
}
~ElegooPrinterWebViewHandler() override
{
stop_upload = true;
if (upload_thread.joinable())
upload_thread.join();
if (sn_thread.joinable())
sn_thread.join();
}
void on_script_message(wxWebViewEvent &evt) override
{
const wxString message = evt.GetString();
if (message.empty())
return;
json root = json::parse(message.ToUTF8().data(), nullptr, false);
if (root.is_discarded() || !root.is_object())
return;
std::string request_id = json_string(root, "id");
std::string method = json_string(root, "method");
json params = root.contains("params") && root["params"].is_object() ? root["params"] : json::object();
if (method.empty()) {
method = json_string(root, "command");
if (params.empty() && root.contains("data") && root["data"].is_object())
params = root["data"];
}
if (method == "open" || method == "common_openurl") {
const std::string url = json_string(params, "url").empty() ? json_string(root, "url") : json_string(params, "url");
if (!url.empty())
wxLaunchDefaultBrowser(url);
if (!request_id.empty())
send_ipc_message("response", request_id, method, 0, "success");
return;
}
if (method == "upload_file") {
handle_upload_request(request_id, method, dump_json(params));
return;
}
if (method == "open_file_dialog") {
handle_open_file_dialog_request(request_id, method, dump_json(params));
return;
}
if (method == "get_sn") {
handle_get_sn_request(request_id, method);
return;
}
}
private:
void send_ipc_message(const char* type, const std::string& request_id, const std::string& method, int code,
const std::string& message, const std::string& data_json = "{}")
{
if (browser() == nullptr)
return;
json body = json::object();
body["type"] = type;
if (!request_id.empty())
body["id"] = request_id;
if (!method.empty())
body["method"] = method;
json data = json::parse(data_json, nullptr, false);
if (data.is_discarded())
data = json::object();
body["data"] = std::move(data);
if (std::string(type) == "response") {
body["code"] = code;
body["message"] = message;
}
const wxString payload = wxString::FromUTF8(dump_json(body));
const wxString script = "if (typeof HandleStudio === 'function') { HandleStudio(" + payload + "); } else { window.postMessage(" + payload + ", '*'); }";
wxGetApp().CallAfter([this, script]() {
if (browser() != nullptr)
WebView::RunScript(browser(), script);
});
}
void handle_upload_request(const std::string& request_id, const std::string& method, const std::string& params_json)
{
if (upload_in_progress.exchange(true)) {
send_ipc_message("response", request_id, method, 1, "Upload already in progress");
return;
}
if (upload_thread.joinable())
upload_thread.join();
json params = json::parse(params_json, nullptr, false);
if (params.is_discarded())
params = json::object();
std::string file_path = json_string(params, "filePath");
std::string file_name = json_string(params, "fileName");
if (file_path.empty()) {
upload_in_progress = false;
send_ipc_message("response", request_id, method, 1, "Missing filePath");
return;
}
// HTML IPC passes UTF-8 strings; decode explicitly to avoid Windows codepage issues.
boost::filesystem::path source_path = path_from_utf8(file_path);
if (file_name.empty())
file_name = filename_to_utf8(source_path);
DynamicPrintConfig* config = get_active_printer_config();
std::unique_ptr<PrintHost> print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
if (print_host == nullptr) {
upload_in_progress = false;
send_ipc_message("response", request_id, method, 1, "Could not get a valid Printer Host reference");
return;
}
stop_upload = false;
upload_thread = std::thread([this, request_id, method, file_path, file_name, source_path, print_host = std::move(print_host)]() mutable {
std::string error_message;
PrintHostUpload upload_data;
upload_data.use_3mf = false;
upload_data.post_action = PrintHostPostUploadAction::None;
upload_data.source_path = source_path;
upload_data.upload_path = path_from_utf8(file_name);
const bool success = print_host->upload(
std::move(upload_data),
[this, request_id](Http::Progress progress, bool& cancel) {
cancel = stop_upload.load();
json data = {
{"uploadedBytes", static_cast<uint64_t>(progress.ulnow)},
{"totalBytes", static_cast<uint64_t>(progress.ultotal)}
};
send_ipc_message("event", request_id, "upload_progress", 0, "", dump_json(data));
},
[&error_message](wxString error) {
error_message = error.ToUTF8().data();
},
[this, request_id](wxString tag, wxString status) {
json data = {
{"tag", tag.ToUTF8().data()},
{"status", status.ToUTF8().data()}
};
send_ipc_message("event", request_id, "upload_info", 0, "", dump_json(data));
});
upload_in_progress = false;
if (success) {
json data = {
{"success", true},
{"filePath", file_path},
{"fileName", file_name}
};
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
} else {
if (error_message.empty())
error_message = "Upload failed";
send_ipc_message("response", request_id, method, 1, error_message);
}
});
}
void handle_open_file_dialog_request(const std::string& request_id, const std::string& method, const std::string& params_json)
{
json params = json::parse(params_json, nullptr, false);
if (params.is_discarded())
params = json::object();
const std::string filter = json_string(params, "filter").empty() ? "All files (*.*)|*.*" : json_string(params, "filter");
wxWindow* parent = owner().GetParent();
if (parent == nullptr)
parent = wxGetApp().GetTopWindow();
wxFileDialog open_file_dialog(parent, _L("Open File"), "", "", wxString::FromUTF8(filter), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
json data = json::object();
data["files"] = json::array();
if (open_file_dialog.ShowModal() != wxID_CANCEL)
data["files"].push_back(open_file_dialog.GetPath().ToUTF8().data());
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
}
void handle_get_sn_request(const std::string& request_id, const std::string& method)
{
if (sn_request_in_progress.exchange(true)) {
send_ipc_message("response", request_id, method, 1, "SN request already in progress");
return;
}
if (sn_thread.joinable())
sn_thread.join();
sn_thread = std::thread([this, request_id, method]() {
std::string sn;
DynamicPrintConfig* config = get_active_printer_config();
std::unique_ptr<PrintHost> print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
if (print_host != nullptr)
sn = print_host->get_sn();
sn_request_in_progress = false;
json data = {
{"sn", sn}
};
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
});
}
std::atomic<bool> upload_in_progress { false };
std::atomic<bool> sn_request_in_progress { false };
std::atomic<bool> stop_upload { false };
std::thread upload_thread;
std::thread sn_thread;
};
} // namespace
std::unique_ptr<PrinterWebViewHandler> create_printer_webview_handler(PrinterWebView& owner)
{
auto cfg = get_active_printer_config();
if(cfg == nullptr) return nullptr;
const auto host_type = cfg->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
switch (host_type)
{
case PrintHostType::htElegooLink:
return std::make_unique<ElegooPrinterWebViewHandler>(owner);
default:
return nullptr;
}
}
} // GUI
} // Slic3r
+36
View File
@@ -0,0 +1,36 @@
#ifndef slic3r_PrinterWebViewHandler_hpp_
#define slic3r_PrinterWebViewHandler_hpp_
#include <memory>
#include <wx/webview.h>
#include <wx/string.h>
class wxWebView;
namespace Slic3r {
namespace GUI {
class PrinterWebView;
class PrinterWebViewHandler {
public:
explicit PrinterWebViewHandler(PrinterWebView& owner);
virtual ~PrinterWebViewHandler();
virtual void on_loaded(wxWebViewEvent &evt);
virtual void on_script_message(wxWebViewEvent &evt);
protected:
PrinterWebView& owner() const;
wxWebView* browser() const;
private:
PrinterWebView& m_owner;
};
std::unique_ptr<PrinterWebViewHandler> create_printer_webview_handler(PrinterWebView& owner);
} // GUI
} // Slic3r
#endif /* slic3r_PrinterWebViewHandler_hpp_ */
+5 -2
View File
@@ -2526,7 +2526,10 @@ void SelectMachineDialog::on_send_print()
|| m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL)
: m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL;
m_print_job->could_emmc_print = obj_->is_support_print_with_emmc;
m_print_job->could_emmc_print = obj_->can_use_emmc_print();
if (obj_->is_support_print_with_emmc && !m_print_job->could_emmc_print) {
BOOST_LOG_TRIVIAL(info) << "print_job: emmc print disabled by config";
}
bool timelapse_option = m_checkbox_list["timelapse"]->IsShown()?true:false;
@@ -3327,7 +3330,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
/*check sdcard when if lan mode printer*/
if (obj_->is_lan_mode_printer()) {
if (obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD
&& !obj_->is_support_print_with_emmc) {
&& !obj_->can_use_emmc_print()) {
show_status(PrintDialogStatus::PrintStatusLanModeNoSdcard);
return;
} else if (obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_READONLY) {
+2 -2
View File
@@ -335,12 +335,12 @@ SelectMachinePopup::SelectMachinePopup(wxWindow *parent)
#if defined(__WINDOWS__)
m_sizer_search_bar = new wxBoxSizer(wxVERTICAL);
m_search_bar = new wxSearchCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_search_bar = new wxSearchCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxNO_BORDER );
m_search_bar->SetDescriptiveText(_L("Search"));
m_search_bar->ShowSearchButton( true );
m_search_bar->ShowCancelButton( false );
m_sizer_search_bar->Add( m_search_bar, 1, wxALL| wxEXPAND, 1 );
m_sizer_main->Add(m_sizer_search_bar, 0, wxALL | wxEXPAND, FromDIP(2));
m_sizer_main->Add(m_sizer_search_bar, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(1));
m_search_bar->Bind( wxEVT_COMMAND_TEXT_UPDATED, &SelectMachinePopup::update_machine_list, this );
#endif
auto own_title = create_title_panel(_L("My Device"));
+35 -20
View File
@@ -11,6 +11,7 @@
#include "Widgets/Button.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/DialogButtons.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include <chrono>
using namespace Slic3r;
@@ -19,9 +20,9 @@ using namespace Slic3r::GUI;
static int _scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; }
static int _ITEM_WIDTH() { return _scale(30); }
#define MIN_DIALOG_WIDTH FromDIP(400)
#define SLIDER_WIDTH FromDIP(200)
#define SLIDER_WIDTH FromDIP(220)
#define SLIDER_HEIGHT FromDIP(25)
#define TEXT_CTRL_WIDTH FromDIP(70)
#define TEXT_CTRL_WIDTH FromDIP(100)
#define BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24))
#define BUTTON_BORDER FromDIP(int(400 - 58 * 2) / 8)
#define SLIDER_SCALE(val) ((val) / 0.001)
@@ -29,7 +30,7 @@ static int _ITEM_WIDTH() { return _scale(30); }
#define SLIDER_SCALE_10(val) ((val) / 0.01)
#define SLIDER_UNSCALE_10(val) ((val) * 0.01)
#define LEFT_RIGHT_PADING FromDIP(20)
#define FONT_COLOR wxColour("#6B6B6B")
#define FONT_COLOR wxColour("#262E30")
wxDEFINE_EVENT(wxEVT_THREAD_DONE, wxCommandEvent);
@@ -85,9 +86,9 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
SetBackgroundColour(*wxWHITE);
wxBoxSizer* bSizer = new wxBoxSizer(wxVERTICAL);
bSizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1));
//bSizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1));
auto image_bitmap = create_scaled_bitmap("step_mesh_info", this, FromDIP(120));
//auto image_bitmap = create_scaled_bitmap("step_mesh_info", this, FromDIP(120));
// wxPanel* overlay_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, image_bitmap.GetSize(), wxTAB_TRAVERSAL);
// overlay_panel->SetBackgroundStyle(wxBG_STYLE_PAINT);
@@ -114,23 +115,31 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
// bSizer->Add(overlay_panel, 0, wxALIGN_CENTER | wxALL, 10);
auto tip_frame = new RoundedRectangle(this, StateColor::darkModeColorFor(wxColour("#F1F1F1")), wxDefaultPosition, wxSize(-1,-1), 6, 0);
wxBoxSizer* tips_sizer = new wxBoxSizer(wxVERTICAL);
wxStaticText* info = new wxStaticText(this, wxID_ANY, _L("Smaller linear and angular deflections result in higher-quality transformations but increase the processing time."));
info->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
wxStaticText* info = new wxStaticText(tip_frame, wxID_ANY, _L("Smaller linear and angular deflections result in higher-quality transformations but increase the processing time."));
info->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
info->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F1F1F1")));
info->SetFont(::Label::Body_14);
info->Wrap(FromDIP(450));
// ORCA standardized HyperLink
HyperLink *tips = new HyperLink(this, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/import_export#step");
tips->SetFont(::Label::Body_12);
info->Wrap(FromDIP(400));
tips_sizer->Add(info, 0, wxALIGN_LEFT);
tips_sizer->Add(tips, 0, wxALIGN_LEFT);
bSizer->Add(tips_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
HyperLink *tips = new HyperLink(tip_frame, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/import_export#step");
tips->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F1F1F1")));
tips_sizer->Add(info, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(10));
tips_sizer->Add(tips, 0, wxALL, FromDIP(10));
tip_frame->SetSizer(tips_sizer);
tip_frame->Layout();
bSizer->Add(tip_frame, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
wxBoxSizer* linear_sizer = new wxBoxSizer(wxHORIZONTAL);
//linear_sizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1));
wxStaticText* linear_title = new wxStaticText(this,
wxID_ANY, _L("Linear Deflection") + ": ");
linear_title->SetFont(::Label::Body_14);
linear_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
linear_sizer->Add(linear_title, 0, wxALIGN_LEFT);
linear_sizer->AddStretchSpacer(1);
@@ -142,7 +151,7 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
linear_sizer->Add(linear_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
auto linear_input = new ::TextInput(this, m_linear_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER);
linear_input->GetTextCtrl()->SetFont(Label::Body_12);
linear_input->GetTextCtrl()->SetFont(Label::Body_14);
linear_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
linear_sizer->Add(linear_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
linear_input->Bind(wxEVT_KILL_FOCUS, ([this, linear_input](wxFocusEvent& e) {
@@ -179,11 +188,13 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
e.Skip();
}));
bSizer->Add(linear_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
bSizer->Add(linear_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
bSizer->AddSpacer(FromDIP(5));
wxBoxSizer* angle_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText* angle_title = new wxStaticText(this,
wxID_ANY, _L("Angle Deflection") + ": ");
angle_title->SetFont(::Label::Body_14);
angle_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
angle_sizer->Add(angle_title, 0, wxALIGN_LEFT);
angle_sizer->AddStretchSpacer(1);
@@ -195,7 +206,7 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
angle_sizer->Add(angle_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
auto angle_input = new ::TextInput(this, m_angle_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER);
angle_input->GetTextCtrl()->SetFont(Label::Body_12);
angle_input->GetTextCtrl()->SetFont(Label::Body_14);
angle_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
angle_sizer->Add(angle_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
angle_input->Bind(wxEVT_KILL_FOCUS, ([this, angle_input](wxFocusEvent& e) {
@@ -233,28 +244,32 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
e.Skip();
}));
bSizer->Add(angle_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
bSizer->Add(angle_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL);
m_split_compound_checkbox = new wxCheckBox(this, wxID_ANY, _L("Split compound and compsolid into multiple objects"), wxDefaultPosition, wxDefaultSize, 0);
m_split_compound_checkbox->SetFont(::Label::Body_14);
m_split_compound_checkbox->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
m_split_compound_checkbox->SetValue(wxGetApp().app_config->get_bool("is_split_compound"));
check_sizer->Add(m_split_compound_checkbox, 0, wxALIGN_LEFT);
bSizer->Add(check_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
bSizer->Add(check_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
wxBoxSizer* mesh_face_number_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText *mesh_face_number_title = new wxStaticText(this, wxID_ANY, _L("Number of triangular facets") + ": ");
mesh_face_number_title->SetFont(::Label::Body_14);
mesh_face_number_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
mesh_face_number_text = new wxStaticText(this, wxID_ANY, "0");
mesh_face_number_text->SetFont(::Label::Body_14);
mesh_face_number_text->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
mesh_face_number_text->SetMinSize(wxSize(FromDIP(150), -1));
mesh_face_number_sizer->Add(mesh_face_number_title, 0, wxALIGN_LEFT);
mesh_face_number_sizer->Add(mesh_face_number_text, 0, wxALIGN_LEFT);
bSizer->Add(mesh_face_number_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
bSizer->Add(mesh_face_number_sizer, 0, wxEXPAND | wxALL, LEFT_RIGHT_PADING);
wxBoxSizer* bSizer_button = new wxBoxSizer(wxHORIZONTAL);
bSizer_button->SetMinSize(wxSize(FromDIP(100), -1));
m_checkbox = new wxCheckBox(this, wxID_ANY, _L("Don't show again"), wxDefaultPosition, wxDefaultSize, 0);
m_checkbox->SetFont(::Label::Body_14);
m_checkbox->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
bSizer_button->Add(m_checkbox, 0, wxALIGN_LEFT | wxLEFT | wxALIGN_CENTER_VERTICAL, LEFT_RIGHT_PADING);
bSizer_button->AddStretchSpacer(1);
+1 -1
View File
@@ -8107,7 +8107,7 @@ void Tab::switch_excluder(int extruder_id)
{}, {"", "filament_extruder_variant"}, // Preset::TYPE_FILAMENT filament don't use id anymore
{}, {"printer_extruder_id", "printer_extruder_variant"}, // Preset::TYPE_PRINTER
};
if (!m_variant_combo && (extruder_id >= nozzle_volumes->size() || extruder_id >= extruders->size()))
if (!m_variant_combo && (extruder_id >= (int)nozzle_volumes->size() || extruder_id >= (int)extruders->size()))
extruder_id = 0;
if (m_extruder_switch && m_type != Preset::TYPE_PRINTER) {
int current_extruder = m_extruder_switch->GetValue() ? 1 : 0;
+26
View File
@@ -1009,6 +1009,32 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
// "Save" button
if (ActionButtons::SAVE & m_buttons) add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save"), false);
if (dependent_presets != nullptr) {
const wxString previous_profile = from_u8(dependent_presets->get_edited_preset().name);
const wxString new_profile = new_selected_preset.empty() ? _L("the new profile") : from_u8(new_selected_preset);
if (m_discard_btn) {
m_discard_btn->SetToolTip(format_wxstr(
_L("Switch to\n\"%1%\"\ndiscarding any changes made in\n\"%2%\"."),
new_profile,
previous_profile));
}
if (m_transfer_btn) {
m_transfer_btn->SetToolTip(format_wxstr(
_L("All \"New Value\" settings modified in\n\"%1%\"\nwill be transferred to\n\"%2%\"."),
previous_profile,
new_profile));
}
if (m_save_btn) {
m_save_btn->SetToolTip(format_wxstr(
_L("All \"New Value\" settings are saved in\n\"%1%\"\nand \"%2%\" will open without any changes."),
previous_profile,
new_profile));
}
}
/* ScalableButton *cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24);
buttons->Add(cancel_btn, 1, wxLEFT | wxRIGHT, 5);
cancel_btn->SetFont(btn_font);*/
+1 -1
View File
@@ -366,7 +366,7 @@ wxString WipingDialog::BuildTextObjStr(bool multi_language)
WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
wxDialog(parent, wxID_ANY, _(L("Flushing volumes for filament change")),
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE ),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
m_max_flush_volume(max_flush_volume)
{
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
+1 -1
View File
@@ -1518,6 +1518,7 @@ FlowRateCalibrationDialog::FlowRateCalibrationDialog(wxWindow* parent, wxWindowI
Layout();
Fit();
v_sizer->SetSizeHints(this);
}
FlowRateCalibrationDialog::~FlowRateCalibrationDialog() {
@@ -1540,7 +1541,6 @@ void FlowRateCalibrationDialog::on_start(wxCommandEvent& event) {
void FlowRateCalibrationDialog::on_dpi_changed(const wxRect& suggested_rect) {
this->Refresh();
Fit();
}
}} // namespace Slic3r::GUI
+9 -105
View File
@@ -169,52 +169,6 @@ static wxString to_wstring_name(std::string name)
return wxEmptyString;
}
static bool is_same_nozzle_diameters(const DynamicPrintConfig &full_config, const MachineObject *obj, wxString& error_msg)
{
if (obj == nullptr)
return true;
try {
std::string nozzle_type;
const ConfigOptionEnumsGenericNullable * config_nozzle_type = full_config.option<ConfigOptionEnumsGenericNullable>("nozzle_type");
std::vector<std::string> config_nozzle_types_str(config_nozzle_type->size());
for (size_t idx = 0; idx < config_nozzle_type->size(); ++idx)
config_nozzle_types_str[idx] = NozzleTypeEumnToStr[NozzleType(config_nozzle_type->values[idx])];
auto opt_nozzle_diameters = full_config.option<ConfigOptionFloats>("nozzle_diameter");
std::vector<float> config_nozzle_diameters(opt_nozzle_diameters->size());
for (size_t idx = 0; idx < opt_nozzle_diameters->size(); ++idx)
config_nozzle_diameters[idx] = opt_nozzle_diameters->values[idx];
std::vector<float> machine_nozzle_diameters(obj->GetExtderSystem()->GetTotalExtderCount());
for (size_t idx = 0; idx < obj->GetExtderSystem()->GetTotalExtderCount(); ++idx)
machine_nozzle_diameters[idx] = obj->GetExtderSystem()->GetNozzleDiameter(idx);
if (config_nozzle_diameters.size() != machine_nozzle_diameters.size()) {
wxString nozzle_in_preset = wxString::Format(_L("nozzle size in preset: %d"), config_nozzle_diameters.size());
wxString nozzle_in_printer = wxString::Format(_L("nozzle size memorized: %d"), machine_nozzle_diameters.size());
error_msg = _L("The size of nozzle type in preset is not consistent with memorized nozzle. Did you change your nozzle lately?") + "\n " + nozzle_in_preset +
"\n " + nozzle_in_printer + "\n";
return false;
}
for (size_t idx = 0; idx < config_nozzle_diameters.size(); ++idx) {
if (config_nozzle_diameters[idx] != machine_nozzle_diameters[idx]) {
wxString nozzle_in_preset = wxString::Format(_L("nozzle[%d] in preset: %.1f"), idx, config_nozzle_diameters[idx]);
wxString nozzle_in_printer = wxString::Format(_L("nozzle[%d] memorized: %.1f"), idx, machine_nozzle_diameters[idx]);
error_msg = _L("Your nozzle type in preset is not consistent with memorized nozzle. Did you change your nozzle lately?") + "\n " + nozzle_in_preset +
"\n " + nozzle_in_printer + "\n";
return false;
}
}
} catch (...) {}
return true;
}
static bool is_same_nozzle_type(const DynamicPrintConfig &full_config, const MachineObject *obj, wxString& error_msg)
{
if (obj == nullptr)
@@ -267,9 +221,6 @@ static bool check_nozzle_diameter_and_type(const DynamicPrintConfig &full_config
if (obj->GetExtderSystem()->GetNozzleType(0) == NozzleType::ntUndefine)
return true;
// if (!is_same_nozzle_diameters(full_config, obj, error_msg))
// return false;
if (!is_same_nozzle_type(full_config, obj, error_msg))
return false;
@@ -1374,7 +1325,6 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
}
bool is_multi_extruder = obj->is_multi_extruders();
Preset *printer_preset = get_printer_preset(obj);
for (const auto &cali_info : cali_infos.calib_datas) {
wxString name = "";
@@ -1393,9 +1343,9 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
return false;
}
float diameter = obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
NozzleFlowType nozzle_volume_type = obj->GetExtderSystem()->GetNozzleFlowType(extruder_id);
if (!is_approx(cali_info.nozzle_diameter, diameter)) {
if (!obj->GetExtderSystem()->NozzleDiameterMatchesOrUnknown(extruder_id, cali_info.nozzle_diameter)) {
if (is_multi_extruder)
error_message = wxString::Format(_L("The currently selected nozzle diameter of %s extruder does not match the actual nozzle diameter.\n"
"Please click the Sync button above and restart the calibration."), name);
@@ -1436,7 +1386,6 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
return true;
bool is_multi_extruder = obj->is_multi_extruders();
Preset *printer_preset = get_printer_preset(obj);
for (const auto &cali_info : cali_infos) {
wxString name = "";
@@ -1460,10 +1409,7 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
return false;
}
float diameter = obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
NozzleFlowType nozzle_volume_type = obj->GetExtderSystem()->GetNozzleFlowType(cali_info.extruder_id);
if (!is_approx(cali_info.nozzle_diameter, diameter)) {
if (!obj->GetExtderSystem()->NozzleDiameterMatchesOrUnknown(extruder_id, cali_info.nozzle_diameter)) {
if (is_multi_extruder)
error_message = wxString::Format(_L("The currently selected nozzle diameter of %s extruder does not match the actual nozzle diameter.\n"
"Please click the Sync button above and restart the calibration."), name);
@@ -1473,6 +1419,9 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
return false;
}
NozzleFlowType nozzle_volume_type = obj->GetExtderSystem()->GetNozzleFlowType(extruder_id);
if (nozzle_volume_type == NozzleFlowType::NONE_FLOWTYPE) {
if (is_multi_extruder)
error_message = wxString::Format(_L("Printer %s nozzle information has not been set. Please configure it before proceeding with the calibration."), name);
@@ -1496,52 +1445,7 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co
bool CalibUtils::check_printable_status_before_cali(const MachineObject* obj, const CalibInfo& cali_info, wxString& error_message)
{
if (!obj) {
error_message = _L("Need select printer");
return false;
}
const ConfigOptionFloats *nozzle_diameter_config = cali_info.printer_prest->config.option<ConfigOptionFloats>("nozzle_diameter");
float nozzle_diameter = nozzle_diameter_config->values[0];
bool is_multi_extruder = obj->is_multi_extruders();
wxString name = _L("left");
if (cali_info.extruder_id == 0) {
name = _L("right");
}
float diameter = obj->GetExtderSystem()->GetNozzleDiameter(cali_info.extruder_id);
NozzleFlowType nozzle_volume_type = obj->GetExtderSystem()->GetNozzleFlowType(cali_info.extruder_id);
if (!is_approx(nozzle_diameter, diameter)) {
if (is_multi_extruder)
error_message = wxString::Format(_L("The currently selected nozzle diameter of %s extruder does not match the actual nozzle diameter.\n"
"Please click the Sync button above and restart the calibration."), name);
else
error_message = _L("The nozzle diameter does not match the actual printer nozzle diameter.\n"
"Please click the Sync button above and restart the calibration.");
return false;
}
if (nozzle_volume_type == NozzleFlowType::NONE_FLOWTYPE) {
if (is_multi_extruder)
error_message = wxString::Format(_L("Printer %s nozzle information has not been set. Please configure it before proceeding with the calibration."), name);
else
error_message = nozzle_not_set_text;
return false;
}
if (NozzleVolumeType(nozzle_volume_type - 1) != cali_info.nozzle_volume_type) {
if (is_multi_extruder)
error_message = wxString::Format(_L("The currently selected nozzle type of %s extruder does not match the actual printer nozzle type.\n"
"Please click the Sync button above and restart the calibration."), name);
else
error_message = nozzle_volume_type_not_match_text;
return false;
}
return true;
return check_printable_status_before_cali(obj, std::vector<CalibInfo>{cali_info}, error_message);
}
bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &full_config, const Calib_Params &params, wxString &error_message)
@@ -1839,7 +1743,7 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
? (print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL
|| print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL)
: print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL;
print_job->could_emmc_print = obj_->is_support_print_with_emmc;
print_job->could_emmc_print = obj_->can_use_emmc_print();
print_job->set_print_config(MachineBedTypeString[bed_type], true, false, false, false, true, false, 0, 0, 0);
print_job->set_print_job_finished_event(wxGetApp().plater()->get_send_calibration_finished_event(), print_job->m_project_name);
@@ -1962,7 +1866,7 @@ void CalibUtils::send_to_print(const std::vector<CalibInfo> &calib_infos, wxStri
? (print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL
|| print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL)
: print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL;
print_job->could_emmc_print = obj_->is_support_print_with_emmc;
print_job->could_emmc_print = obj_->can_use_emmc_print();
print_job->set_print_config(MachineBedTypeString[bed_type], true, true, false, false, true, false, 0, 1, 0);
print_job->set_print_job_finished_event(wxGetApp().plater()->get_send_calibration_finished_event(), print_job->m_project_name);
+465 -37
View File
@@ -11,6 +11,7 @@
#include <boost/asio.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
@@ -58,6 +59,69 @@ namespace Slic3r {
namespace {
constexpr const char* ELEGOO_CC2_DEFAULT_TOKEN = "123456";
enum class ElegooPrinterType {
Other,
CC,
CC2,
};
ElegooPrinterType classify_printer_model(const std::string& printer_model)
{
if (!boost::algorithm::starts_with(printer_model, "Elegoo Centauri"))
return ElegooPrinterType::Other;
const auto last_char = printer_model.find_last_not_of(" \t\r\n");
if (last_char != std::string::npos && printer_model[last_char] == '2')
return ElegooPrinterType::CC2;
return ElegooPrinterType::CC;
}
std::string get_cc2_token(const std::string& apikey)
{
return apikey.empty() ? ELEGOO_CC2_DEFAULT_TOKEN : apikey;
}
bool parse_cc2_response(const std::string& body, std::string& error_message, std::string* serial_number = nullptr)
{
try {
pt::ptree root;
std::istringstream is(body);
pt::read_json(is, root);
const int error_code = root.get<int>("error_code", -1);
if (error_code != 0) {
error_message = root.get<std::string>("message", "Printer returned an error");
if (error_message.empty())
error_message = "Printer returned an error";
error_message += " (" + std::to_string(error_code) + ")";
return false;
}
if (serial_number != nullptr) {
const auto system_info = root.get_child_optional("system_info");
if (!system_info) {
error_message = "Missing system_info in response";
return false;
}
const auto sn = system_info->get_optional<std::string>("sn");
if (!sn || sn->empty()) {
error_message = "Missing printer serial number in response";
return false;
}
*serial_number = *sn;
}
return true;
} catch (const std::exception&) {
error_message = "Error parsing response";
return false;
}
}
std::string get_host_from_url(const std::string& url_in)
{
std::string url = url_in;
@@ -220,15 +284,126 @@ namespace Slic3r {
return ret_val;
}
std::string path_to_utf8(const boost::filesystem::path& path)
{
#ifdef WIN32
return boost::nowide::narrow(path.wstring());
#else
return path.string();
#endif
}
std::string filename_to_utf8(const boost::filesystem::path& path)
{
#ifdef WIN32
return boost::nowide::narrow(path.filename().wstring());
#else
return path.filename().string();
#endif
}
} //namespace
ElegooLink::ElegooLink(DynamicPrintConfig *config):
OctoPrint(config) {
OctoPrint(config), m_printerModel(config->opt_string("printer_model")) {
}
std::string ElegooLink::get_print_host_webui(DynamicPrintConfig* config)
{
if (config == nullptr)
return {};
std::string fallback_webui = config->opt_string("print_host_webui");
if (fallback_webui.empty())
fallback_webui = config->opt_string("print_host");
if (!fallback_webui.empty()) {
const bool has_http_scheme = boost::algorithm::istarts_with(fallback_webui, "http");
const bool has_file_scheme = boost::algorithm::istarts_with(fallback_webui, "file:");
if (!has_http_scheme && !has_file_scheme)
fallback_webui = "http://" + fallback_webui;
}
const std::string host = config->opt_string("print_host");
if (host.empty())
return fallback_webui;
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
return fallback_webui;
std::string web_path = resources_dir() + "/plugins/elegoolink/web/lan_service_web/index.html";
std::replace(web_path.begin(), web_path.end(), '\\', '/');
web_path = "file://" + web_path;
web_path += "?access_code=" + get_cc2_token(config->opt_string("printhost_apikey"));
web_path += "&ip=" + get_host_from_url(host) + "&id=elegoo_123456";
const std::string lang = GUI::wxGetApp().current_language_code_safe().utf8_string();
if (!lang.empty())
web_path += "&lang=" + lang;
if (GUI::get_app_config()->get_bool("developer_mode"))
web_path += "&dev=true";
return web_path;
}
std::string ElegooLink::cc2_token() const
{
return get_cc2_token(m_apikey);
}
std::string ElegooLink::make_cc2_info_url() const
{
return make_url("system/info?X-Token=" + escape_string(cc2_token()));
}
std::string ElegooLink::make_cc2_upload_url() const
{
return make_url("upload");
}
const char* ElegooLink::get_name() const { return "ElegooLink"; }
PrintHostPostUploadActions ElegooLink::get_post_upload_actions() const
{
if (classify_printer_model(m_printerModel) == ElegooPrinterType::CC2) {
return PrintHostPostUploadAction::None;
} else {
return PrintHostPostUploadAction::StartPrint;
}
}
std::string ElegooLink::get_sn() const
{
if (classify_printer_model(m_printerModel) != ElegooPrinterType::CC2)
return "";
const char* name = get_name();
std::string sn;
const auto token = cc2_token();
auto http = Http::get(make_cc2_info_url());
http.timeout_connect(10)
.timeout_max(15);
http.header("X-Token", token);
http.header("Accept", "application/json");
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info for SN: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
})
.on_complete([&](std::string body, unsigned status) {
std::string error_message;
if (!parse_cc2_response(body, error_message, &sn)) {
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Failed to parse CC2 SN response, HTTP %2%, reason: %3%") % name % status % error_message;
sn.clear();
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif // WIN32
.perform_sync();
return sn;
}
bool ElegooLink::elegoo_test(wxString& msg) const{
@@ -268,11 +443,54 @@ namespace Slic3r {
return res;
}
bool ElegooLink::test(wxString &curl_msg) const{
if(OctoPrint::test(curl_msg)){
return true;
switch (classify_printer_model(m_printerModel)) {
case ElegooPrinterType::Other:
return OctoPrint::test(curl_msg);
case ElegooPrinterType::CC2:
return elegoo_cc2_test(curl_msg);
case ElegooPrinterType::CC:
return elegoo_test(curl_msg);
}
curl_msg="";
return elegoo_test(curl_msg);
return false;
}
bool ElegooLink::elegoo_cc2_test(wxString& msg) const
{
const char* name = get_name();
bool res = true;
const auto token = cc2_token();
auto url = make_cc2_info_url();
auto http = Http::get(std::move(url));
http.header("X-Token", token);
http.header("Accept", "application/json");
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
if (status == 401 || status == 403)
msg = format_error(body, "Invalid access code", status);
else
msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
})
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got CC2 device info: %2%") % name % body;
std::string error_message;
std::string serial_number;
if (!parse_cc2_response(body, error_message, &serial_number)) {
res = false;
msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
return;
}
res = true;
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
.on_ip_resolve([&](std::string address) {
msg = GUI::from_u8(address);
})
#endif // WIN32
.perform_sync();
return res;
}
#ifdef WIN32
@@ -320,12 +538,53 @@ namespace Slic3r {
}
bool ElegooLink::test_with_resolved_ip(wxString& msg) const
{
// Elegoo supports both otcoprint and Elegoo link
if (OctoPrint::test_with_resolved_ip(msg)) {
return true;
switch (classify_printer_model(m_printerModel)) {
case ElegooPrinterType::Other:
return OctoPrint::test_with_resolved_ip(msg);
case ElegooPrinterType::CC2:
return elegoo_cc2_test_with_resolved_ip(msg);
case ElegooPrinterType::CC:
return elegoo_test_with_resolved_ip(msg);
}
msg = "";
return elegoo_test_with_resolved_ip(msg);
return false;
}
bool ElegooLink::elegoo_cc2_test_with_resolved_ip(wxString& msg) const
{
const char* name = get_name();
bool res = true;
const auto token = cc2_token();
auto url = substitute_host(make_cc2_info_url(), GUI::into_u8(msg));
std::string host_header = get_host_from_url(m_host);
auto http = Http::get(url);
msg.Clear();
http.header("Host", host_header);
http.header("X-Token", token);
http.header("Accept", "application/json");
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info at %2% : %3%, HTTP %4%, body: `%5%`") % name % url %
error % status % body;
res = false;
if (status == 401 || status == 403)
msg = format_error(body, "Invalid access code", status);
else
msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
})
.on_complete([&](std::string body, unsigned status) {
std::string error_message;
std::string serial_number;
if (!parse_cc2_response(body, error_message, &serial_number)) {
res = false;
msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
return;
}
res = true;
})
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
.perform_sync();
return res;
}
#endif // WIN32
@@ -343,25 +602,31 @@ namespace Slic3r {
#ifdef WIN32
bool ElegooLink::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const
{
wxString test_msg_or_host_ip = "";
const auto printer_type = classify_printer_model(m_printerModel);
if (printer_type == ElegooPrinterType::Other)
return OctoPrint::upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr);
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
// Test_msg already contains resolved ip and will be cleared on start of test().
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
//Elegoo supports both otcoprint and Elegoo link
if(OctoPrint::test_with_resolved_ip(test_msg_or_host_ip)){
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
if (printer_type == ElegooPrinterType::CC2) {
wxString cc2_msg = GUI::from_u8(resolved_addr.to_string());
if (!elegoo_cc2_test_with_resolved_ip(cc2_msg)) {
error_fn(std::move(cc2_msg));
return false;
}
std::string url = substitute_host(make_cc2_upload_url(), resolved_addr.to_string());
info_fn(L"resolve", boost::nowide::widen(url));
return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
}
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
if(!elegoo_test_with_resolved_ip(test_msg_or_host_ip)){
error_fn(std::move(test_msg_or_host_ip));
wxString legacy_msg = GUI::from_u8(resolved_addr.to_string());
if (!elegoo_test_with_resolved_ip(legacy_msg)) {
error_fn(std::move(legacy_msg));
return false;
}
std::string url = substitute_host(make_url("uploadFile/upload"), resolved_addr.to_string());
info_fn(L"resolve", boost::nowide::widen(url));
@@ -372,23 +637,46 @@ namespace Slic3r {
bool ElegooLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
wxString test_msg_or_host_ip;
//Elegoo supports both otcoprint and Elegoo link
if(OctoPrint::test(test_msg_or_host_ip)){
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
const auto printer_type = classify_printer_model(m_printerModel);
if (printer_type == ElegooPrinterType::Other)
return OctoPrint::upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
if (printer_type == ElegooPrinterType::CC2) {
wxString cc2_msg;
if (!elegoo_cc2_test(cc2_msg)) {
error_fn(std::move(cc2_msg));
return false;
}
std::string url;
#ifdef WIN32
if (m_host.find("https://") == 0 || cc2_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
url = make_cc2_upload_url();
}
#ifdef WIN32
else {
info_fn(L"resolve", cc2_msg);
url = substitute_host(make_cc2_upload_url(), GUI::into_u8(cc2_msg));
BOOST_LOG_TRIVIAL(info) << "CC2 upload address after ip resolve: " << url;
}
#endif // _WIN32
return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
}
test_msg_or_host_ip="";
if(!elegoo_test(test_msg_or_host_ip)){
error_fn(std::move(test_msg_or_host_ip));
wxString legacy_msg;
if(!elegoo_test(legacy_msg)){
error_fn(std::move(legacy_msg));
return false;
}
std::string url;
#ifdef WIN32
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
if (m_host.find("https://") == 0 || legacy_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
// If https is entered we assume signed ceritificate is being used
@@ -403,8 +691,8 @@ namespace Slic3r {
// This new address returns in "test_msg_or_host_ip" variable.
// Solves troubles of uploades failing with name address.
// in original address (m_host) replace host for resolved ip
info_fn(L"resolve", test_msg_or_host_ip);
url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(test_msg_or_host_ip));
info_fn(L"resolve", legacy_msg);
url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(legacy_msg));
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
}
#endif // _WIN32
@@ -490,11 +778,11 @@ namespace Slic3r {
bool ElegooLink::loopUpload(std::string url, PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char* name = get_name();
const auto upload_filename = upload_data.upload_path.filename().string();
std::string source_path = upload_data.source_path.string();
const auto upload_filename = filename_to_utf8(upload_data.upload_path);
std::string source_path = path_to_utf8(upload_data.source_path);
// calc file size
std::ifstream file(source_path, std::ios::binary | std::ios::ate);
boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.close();
const std::string fileSize = std::to_string(size);
@@ -583,6 +871,146 @@ namespace Slic3r {
return res;
}
bool ElegooLink::uploadPartCC2(Http& http,
const std::string& host_header,
const std::string& token,
const std::string& md5,
const boost::filesystem::path& path,
const std::string& filename,
size_t filesize,
size_t offset,
size_t length,
ProgressFn prorgess_fn,
ErrorFn error_fn) const
{
const char* name = get_name();
boost::nowide::ifstream file(path, std::ios::binary);
if (!file.is_open()) {
error_fn(_L("Failed to open file for upload."));
return false;
}
file.seekg(static_cast<std::streamoff>(offset), std::ios::beg);
std::string chunk(length, '\0');
file.read(chunk.data(), static_cast<std::streamsize>(length));
if (!file && static_cast<size_t>(file.gcount()) != length) {
error_fn(_L("Failed to read file chunk for upload."));
return false;
}
chunk.resize(static_cast<size_t>(file.gcount()));
const size_t end_offset = offset + chunk.size() - 1;
const auto range = std::string("bytes ") + std::to_string(offset) + "-" + std::to_string(end_offset) + "/" + std::to_string(filesize);
bool result = false;
http.headers_reset();
if (!host_header.empty())
http.header("Host", host_header);
http.header("Accept", "application/json")
.header("Content-Type", "application/octet-stream")
.header("Content-Length", std::to_string(chunk.size()))
.header("Content-Range", range)
.header("X-File-Name", filename)
.header("X-File-MD5", md5)
.header("X-Token", token)
.set_post_body(chunk)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: CC2 chunk uploaded: HTTP %2%: %3%") % name % status % body;
std::string error_message;
if (!parse_cc2_response(body, error_message)) {
error_fn(format_error(body, error_message.empty() ? "CC2 upload failed" : error_message, status));
return;
}
result = true;
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading CC2 chunk: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
if (status == 401 || status == 403)
error_fn(format_error(body, "Invalid access code", status));
else
error_fn(format_error(body, error.empty() ? "CC2 upload failed" : error, status));
})
.on_progress([&](Http::Progress progress, bool& cancel) {
if (progress.ultotal == progress.ulnow)
return;
prorgess_fn(std::move(progress), cancel);
if (cancel)
BOOST_LOG_TRIVIAL(info) << name << ": CC2 upload canceled";
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync();
return result;
}
bool ElegooLink::loopUploadCC2(std::string url,
const std::string& host_header,
PrintHostUpload upload_data,
ProgressFn progress_fn,
ErrorFn error_fn,
InfoFn info_fn) const
{
BOOST_LOG_TRIVIAL(info) << get_name() << ": Uploading file to Elegoo CC2";
const auto upload_filename = filename_to_utf8(upload_data.upload_path);
std::string source_path = path_to_utf8(upload_data.source_path);
std::string md5;
bbl_calc_md5(source_path, md5);
std::transform(md5.begin(), md5.end(), md5.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
error_fn(_L("Failed to open file for upload."));
return false;
}
const std::streamsize size = file.tellg();
file.close();
if (size <= 0) {
error_fn(_L("The file is empty or could not be read."));
return false;
}
if (md5.empty()) {
error_fn(_L("Failed to calculate file checksum."));
return false;
}
const std::string token = cc2_token();
const int packageCount = static_cast<int>((size + MAX_UPLOAD_PACKAGE_LENGTH - 1) / MAX_UPLOAD_PACKAGE_LENGTH);
auto http = Http::put2(url);
http.timeout_connect(30)
.timeout_max(180);
bool res = false;
for (int i = 0; i < packageCount; ++i) {
const size_t offset = MAX_UPLOAD_PACKAGE_LENGTH * static_cast<size_t>(i);
size_t length = MAX_UPLOAD_PACKAGE_LENGTH;
if (i == packageCount - 1 && size % MAX_UPLOAD_PACKAGE_LENGTH > 0)
length = static_cast<size_t>(size % MAX_UPLOAD_PACKAGE_LENGTH);
res = uploadPartCC2(
http, host_header, token, md5, source_path, upload_filename, static_cast<size_t>(size), offset, length,
[size, i, progress_fn](Http::Progress progress, bool& cancel) {
const size_t uploaded = static_cast<size_t>(i) * MAX_UPLOAD_PACKAGE_LENGTH + progress.ulnow;
Http::Progress merged(0, 0, static_cast<size_t>(size), std::min(static_cast<size_t>(size), uploaded), progress.buffer,
progress.upload_spd);
progress_fn(merged, cancel);
},
error_fn);
if (!res)
break;
}
if (res && upload_data.post_action == PrintHostPostUploadAction::StartPrint)
BOOST_LOG_TRIVIAL(info) << get_name() << ": CC2 upload completed; start print is not supported.";
(void) info_fn;
return res;
}
bool ElegooLink::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
#ifndef WIN32
+30 -2
View File
@@ -20,14 +20,16 @@ class ElegooLink : public OctoPrint
public:
ElegooLink(DynamicPrintConfig *config);
~ElegooLink() override = default;
static std::string get_print_host_webui(DynamicPrintConfig *config);
const char* get_name() const override;
virtual bool test(wxString &curl_msg) const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
std::string get_sn() const override;
bool has_auto_discovery() const override { return false; }
bool can_test() const override { return true; }
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
PrintHostPostUploadActions get_post_upload_actions() const override;
protected:
#ifdef WIN32
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
@@ -39,9 +41,9 @@ protected:
virtual bool test_with_resolved_ip(wxString& curl_msg) const override;
bool elegoo_test_with_resolved_ip(wxString& curl_msg) const;
#endif
private:
bool elegoo_test(wxString& curl_msg) const;
bool elegoo_cc2_test(wxString& curl_msg) const;
bool print(WebSocketClient& client,
std::string timeLapse,
std::string heatedBedLeveling,
@@ -54,6 +56,12 @@ private:
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
bool loopUploadCC2(std::string url,
const std::string& host_header,
PrintHostUpload upload_data,
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
bool uploadPart(Http &http,
std::string md5,
@@ -66,6 +74,26 @@ private:
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
bool uploadPartCC2(Http& http,
const std::string& host_header,
const std::string& token,
const std::string& md5,
const boost::filesystem::path& path,
const std::string& filename,
size_t filesize,
size_t offset,
size_t length,
ProgressFn prorgess_fn,
ErrorFn error_fn) const;
std::string cc2_token() const;
std::string make_cc2_info_url() const;
std::string make_cc2_upload_url() const;
#ifdef WIN32
bool elegoo_cc2_test_with_resolved_ip(wxString& curl_msg) const;
#endif
std::string m_printerModel;
};
}
+9 -1
View File
@@ -39,9 +39,17 @@ FileTransferModule::FileTransferModule(ModuleHandle networking_module, int requi
FileTransferTunnel::FileTransferTunnel(FileTransferModule &m, const std::string &url) : m_(&m)
{
// Guard against missing symbols in older Bambu networking plugins.
// These symbols were added in a newer plugin ABI; if the installed
// plugin predates them, ft_tunnel_create/ft_tunnel_set_status_cb
// will be null and calling them crashes.
if (!m_->ft_tunnel_create || !m_->ft_tunnel_set_status_cb) {
throw std::runtime_error("Bambu networking plugin is too old: missing ft_tunnel_* symbols. "
"Please update the networking plugin.");
}
FT_TunnelHandle *h{};
if (m_->ft_tunnel_create(url.c_str(), &h) != 0 || !h) {
throw std::runtime_error("ft_tunnel_create failed");
}
h_ = h;
+15
View File
@@ -570,6 +570,21 @@ Http& Http::header(std::string name, const std::string &value)
return *this;
}
Http& Http::headers_reset()
{
if (!p) { return *this; }
::curl_slist_free_all(p->headerlist);
p->headerlist = nullptr;
p->headerlist = curl_slist_append(p->headerlist, "Expect:");
std::lock_guard<std::mutex> l(g_mutex);
for (auto it = extra_headers.begin(); it != extra_headers.end(); ++it)
this->header(it->first, it->second);
return *this;
}
Http& Http::remove_header(std::string name)
{
if (p) {
+2
View File
@@ -109,6 +109,8 @@ public:
Http& set_range(const std::string& range);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
// Clears all custom headers and restores default implicit headers.
Http& headers_reset();
// Removes a header field.
Http& remove_header(std::string name);
// Authorization by HTTP digest, based on RFC2617.
+50 -28
View File
@@ -74,6 +74,32 @@ constexpr const char* SECRET_STORE_SERVICE = "OrcaSlicer/Auth";
constexpr const char* SECRET_STORE_USER = "orca_refresh_token";
constexpr std::chrono::seconds TOKEN_REFRESH_SKEW{900}; // 15 minutes
// Return a JSON field only when it is present as a string. Missing or non-string values normalize to empty.
std::string get_json_string_field(const json& j, const std::string& key)
{
if (j.contains(key) && j[key].is_string()) {
return j[key].get<std::string>();
}
return "";
}
// Resolve the human-facing UI label from provider metadata.
std::string resolve_display_name(
const std::string& display_name,
const std::string& nickname,
const std::string& full_name,
const std::string& name,
const std::string& username)
{
// Providers and payload shapes do not all use the same display-name field.
// Fallback sequence: display_name -> nickname -> full_name -> name
if (!display_name.empty()) return display_name;
if (!nickname.empty()) return nickname;
if (!full_name.empty()) return full_name;
if (!name.empty()) return name;
return username;
}
std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "")
{
if (name.empty()) {
@@ -1667,47 +1693,43 @@ bool OrcaCloudServiceAgent::set_user_session(const std::string& token,
bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool notify_login)
{
auto safe_str = [](const json& j, const std::string& key) -> std::string {
if (j.contains(key) && j[key].is_string()) return j[key].get<std::string>();
return "";
};
std::string access_token = safe_str(session_json, "access_token");
std::string access_token = get_json_string_field(session_json, "access_token");
if (access_token.empty()) {
access_token = safe_str(session_json, "token");
access_token = get_json_string_field(session_json, "token");
}
std::string refresh_token = safe_str(session_json, "refresh_token");
std::string refresh_token = get_json_string_field(session_json, "refresh_token");
std::string user_id, username, nickname, avatar;
if (session_json.contains("user") && session_json["user"].is_object()) {
// Nested format (Orca cloud / GoTrue response)
const auto& user = session_json["user"];
user_id = safe_str(user, "id");
user_id = get_json_string_field(user, "id");
if (user.contains("user_metadata") && user["user_metadata"].is_object()) {
const auto& meta = user["user_metadata"];
username = safe_str(meta, "username"); // Orca Cloud's unique username
username = get_json_string_field(meta, "username"); // Orca Cloud's unique username
nickname = safe_str(meta, "display_name"); // Orca Cloud's primary display name field
// Fallback to different name from different providers if display_name is not set
if (nickname.empty())
nickname = safe_str(meta, "full_name");
if (nickname.empty())
nickname = safe_str(meta, "name");
if (nickname.empty())
nickname = username;
avatar = safe_str(meta, "avatar_url");
// Orca Cloud's primary display name field is display_name.
// Fallback to different names from different providers if display_name is not set.
nickname = resolve_display_name(
get_json_string_field(meta, "display_name"),
get_json_string_field(meta, "nickname"),
get_json_string_field(meta, "full_name"),
get_json_string_field(meta, "name"),
username);
avatar = get_json_string_field(meta, "avatar_url");
}
} else {
// Flat format (WebView direct token flow)
user_id = safe_str(session_json, "user_id");
username = safe_str(session_json, "username");
nickname = safe_str(session_json, "display_name");
if(nickname.empty())
nickname = safe_str(session_json, "nickname");
avatar = safe_str(session_json, "avatar");
user_id = get_json_string_field(session_json, "user_id");
username = get_json_string_field(session_json, "username");
nickname = resolve_display_name(
get_json_string_field(session_json, "display_name"),
get_json_string_field(session_json, "nickname"),
get_json_string_field(session_json, "full_name"),
get_json_string_field(session_json, "name"),
username);
avatar = get_json_string_field(session_json, "avatar");
}
if (access_token.empty() || user_id.empty()) {
+4 -3
View File
@@ -88,8 +88,8 @@ public:
std::string access_token;
std::string refresh_token;
std::string user_id;
// Orca auth semantics: user_name is unique orca cloud username(orca_xxxxx), user_nickname is
// the display name shown in the UI when available.
// Orca auth semantics: user_name is the unique Orca Cloud username (orca_xxxxx),
// user_nickname is the display name shown in the UI when available.
std::string user_name;
std::string user_nickname;
std::string user_avatar;
@@ -276,13 +276,14 @@ public:
bool refresh_now(const std::string& refresh_token, const std::string& reason, bool async = false);
bool refresh_session_with_token(const std::string& refresh_token);
// Session state helpers
// Session state helpers. nickname is the human-facing UI label after provider fallback resolution.
bool set_user_session(const std::string& token,
const std::string& user_id,
const std::string& username,
const std::string& nickname,
const std::string& avatar,
const std::string& refresh_token = "");
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
void clear_session();
+34
View File
@@ -74,6 +74,40 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
}
}
std::string PrintHost::get_print_host_webui(DynamicPrintConfig* config)
{
if (config == nullptr)
return {};
std::string webui_url;
const auto* host_type_opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type");
const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htOctoPrint;
switch (host_type) {
case htElegooLink: {
webui_url = ElegooLink::get_print_host_webui(config);
break;
}
default: break;
}
if (webui_url.empty()) {
webui_url = config->opt_string("print_host_webui");
if (webui_url.empty())
webui_url = config->opt_string("print_host");
if (webui_url.empty())
return webui_url;
}
const bool has_http_scheme = boost::algorithm::istarts_with(webui_url, "http");
const bool has_file_scheme = boost::algorithm::istarts_with(webui_url, "file:");
if (!has_http_scheme && !has_file_scheme)
webui_url = "http://" + webui_url;
return webui_url;
}
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
{
if (status != 0) {
+7
View File
@@ -63,6 +63,12 @@ public:
// A print host usually does not support multiple printers, with the exception of Repetier server.
virtual bool supports_multiple_printers() const { return false; }
virtual std::string get_host() const = 0;
/**
* Get the serial number for connecting to the printer.
* For Elegoo CC2, the device details connection to the printer requires the serial number.
* Other print hosts do not need to implement this interface, and it returns an empty string by default.
*/
virtual std::string get_sn() const { return ""; }
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
// Returns false if not supported. May throw HostNetworkError.
@@ -73,6 +79,7 @@ public:
virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
static PrintHost* get_print_host(DynamicPrintConfig *config);
static std::string get_print_host_webui(DynamicPrintConfig *config);
//Support for cloud webui login
virtual bool is_cloud() const { return false; }
+64 -4
View File
@@ -19,6 +19,50 @@ T safe_at(const std::vector<T>& vec, int index, const T& fallback)
return (index >= 0 && index < static_cast<int>(vec.size())) ? vec[index] : fallback;
}
std::string find_closest_color_preset_by_vendor_and_type(const PresetCollection& filaments,
const std::string& vendor_name,
const std::string& filament_type,
const std::string& color_rgba)
{
std::string best_match_id = "";
int best_color_distance = 0xffffffff;
for (const auto& p : filaments.get_presets()) {
if (p.is_visible && p.is_compatible &&
// Filament profile must be detached from parent to be considered for matching
filaments.get_preset_base(p) == &p && p.config.opt_string("filament_vendor", 0u) == vendor_name &&
p.config.opt_string("filament_type", 0u) == filament_type) {
// The printer returns RGBA in the format RRGGBBAA, but profiles store color as #RRGGBB,
// so we must remove # and ignore alpha channel for distance calculation
unsigned int target_color_value = std::stoul(color_rgba.substr(0, color_rgba.length() - 2), nullptr, 16);
std::string p_color = p.config.opt_string("default_filament_colour", 0u);
unsigned int p_color_value;
if (!p_color.empty()) {
unsigned int hash_pos = p_color.find("#");
p_color_value = std::stoul(p_color.substr(hash_pos != std::string::npos ? hash_pos + 1 : 0), nullptr, 16);
} else {
// Default to black if no color specified in profile. Assume other profiles might be a closer color match.
// Could be a problem if the target color is also black and there exist a specific profile for that type, vendor and color
// combination.
p_color_value = 0;
}
// Calculate Euclidean color distance in RGB space
int dr = ((target_color_value & 0xff) - (p_color_value & 0xff));
int dg = (((target_color_value >> 8) & 0xff) - ((p_color_value >> 8) & 0xff));
int db = (((target_color_value >> 16) & 0xff) - ((p_color_value >> 16) & 0xff));
unsigned int distance = dr * dr + dg * dg + db * db;
if (distance < best_color_distance) {
best_color_distance = distance;
best_match_id = p.filament_id;
}
}
}
return best_match_id;
}
} // anonymous namespace
SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {}
@@ -113,6 +157,7 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id)
auto filament_type = ptc.value("filament_type", std::vector<std::string>{});
auto filament_sub_type = ptc.value("filament_sub_type", std::vector<std::string>{});
auto filament_color = ptc.value("filament_color_rgba", std::vector<std::string>{});
auto filament_vendor = ptc.value("filament_vendor", std::vector<std::string>{});
const int slot_count = static_cast<int>(filament_exist.size());
if (slot_count == 0) {
@@ -141,12 +186,27 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id)
if (tray.has_filament) {
tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str),
safe_at(filament_sub_type, i, empty_str));
auto* bundle = GUI::wxGetApp().preset_bundle;
tray.tray_info_idx = bundle
? bundle->filaments.filament_id_by_type(tray.tray_type)
: map_filament_type_to_generic_id(tray.tray_type);
tray.tray_color = safe_at(filament_color, i, default_color);
auto* bundle = GUI::wxGetApp().preset_bundle;
// Try to find a matching preset for this filament based on vendor, type and color.
// If not found, default to traditional search by type only or generic type mapping.
if (bundle) {
std::string vendor = safe_at(filament_vendor, i, empty_str);
std::string filament_id = find_closest_color_preset_by_vendor_and_type(bundle->filaments, vendor, tray.tray_type,
tray.tray_color);
if (!filament_id.empty()) {
tray.tray_info_idx = filament_id;
BOOST_LOG_TRIVIAL(warning) << "Filament sync: Found manufacturer-specific profile for slot " << i << ": "
<< filament_id;
} else {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
}
// Extract NFC temperature data if available
if (nfc_info.is_array() && i < static_cast<int>(nfc_info.size()) && nfc_info[i].is_object()) {
auto& nfc_slot = nfc_info[i];