mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 02:11:39 +00:00
Merge remote-tracking branch 'upstream/main' into feedback
This commit is contained in:
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 + ": ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" })
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -543,6 +543,7 @@ private:
|
||||
ImVec4 m_TextColor;
|
||||
|
||||
ImVec4 m_HyperTextColor;
|
||||
ImVec4 m_HyperTextColorHover;
|
||||
|
||||
ImVec4 m_ErrorColor;
|
||||
ImVec4 m_WarnColor;
|
||||
|
||||
+24
-67
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¶ms, 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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user