mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 01:41:03 +00:00
Merge upstream, and move the IMEX placeholders onto the new def macro
Eleven commits, including a typed-config G-code export speedup, a printer agent refactor that generalizes the infrastructure beyond Bambu, gyroid optimization, and three CLI crash fixes. One conflict, in PrintConfig.cpp. Upstream introduced a new_def macro and began migrating the placeholder table onto it, adding curr_bed_type that way in the same block where this branch had added imex_mode, imex_mode_index and imex_mode_gcode in the older def = this->add(...) form. Both sides are kept and ours are converted to the macro, which expands to the same three statements and wraps label and tooltip in L() exactly as before, so nothing changes about what is registered or what is translatable. Note for anyone building this branch: the agent refactor adds a dependency, LibDataChannel, so the deps tree needs dep_DataChannel built before the app will configure. A distribution package of the same name will be found first if one is installed, and the resulting error names a missing RelWithDebInfo location rather than the wrong package, so point LibDataChannel_DIR at the dependency prefix if that happens. Verified: 789 targets build clean under -Werror, and the Release suite passes 1665 of 1665, up from 1630 before the merge -- the 35 new cases arrived with it and all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+49
-7
@@ -1913,7 +1913,8 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
|
||||
old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod");
|
||||
@@ -2116,9 +2117,14 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
// One resolver for the whole run, so presets from the same vendor tree share its load.
|
||||
std::unique_ptr<PresetBundle> system_preset_resolver;
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
return *system_preset_resolver;
|
||||
};
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||
return true;
|
||||
@@ -2126,9 +2132,7 @@ int CLI::run(int argc, char **argv)
|
||||
PresetBundle *bundle = nullptr;
|
||||
bool allow_source_manifest = false;
|
||||
if (config_from == "system") {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
bundle = system_preset_resolver.get();
|
||||
bundle = &ensure_system_preset_resolver();
|
||||
allow_source_manifest = true;
|
||||
} else {
|
||||
bundle = ensure_cli_preset_bundle(error);
|
||||
@@ -3120,6 +3124,44 @@ int CLI::run(int argc, char **argv)
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Load the project's printer and process settings as the GUI loads its presets: over the default preset,
|
||||
// with every key the project does not list as changed, including keys saved before an option existed,
|
||||
// taken from its current system preset.
|
||||
auto load_project_preset = [this, &ensure_system_preset_resolver, ¤t_different_settings, filament_count](const std::string &system_name, Preset::Type type) {
|
||||
if (system_name.empty())
|
||||
return;
|
||||
// Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers.
|
||||
static const std::set<std::string> bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition",
|
||||
"compatible_prints_condition", "print_settings_id", "printer_settings_id"};
|
||||
const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0;
|
||||
PresetBundle &resolver = ensure_system_preset_resolver();
|
||||
DynamicPrintConfig system_config;
|
||||
t_config_option_keys keys;
|
||||
const DynamicPrintConfig config = Preset::load_external_config(type,
|
||||
type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config,
|
||||
m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()),
|
||||
[&](const std::string &) -> DynamicPrintConfig * {
|
||||
std::string error;
|
||||
if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error))
|
||||
return &system_config;
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error;
|
||||
return nullptr;
|
||||
}, &keys);
|
||||
for (const std::string &key : keys) {
|
||||
const ConfigOption *opt = config.option(key);
|
||||
const ConfigOption *old = m_print_config.option(key);
|
||||
if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt))
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize();
|
||||
m_print_config.set_key_value(key, opt->clone());
|
||||
}
|
||||
};
|
||||
// The --uptodate path refreshes the project from its own system configs.
|
||||
if (new_printer_name.empty() && load_machine_config.empty())
|
||||
load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER);
|
||||
if (new_process_name.empty() && load_process_config.empty())
|
||||
load_project_preset(current_process_system_name, Preset::TYPE_PRINT);
|
||||
|
||||
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
|
||||
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
|
||||
inherits_group.resize(filament_count + 2, std::string());
|
||||
|
||||
@@ -2702,6 +2702,9 @@ public:
|
||||
virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0;
|
||||
// Collect names of all configuration values maintained by this configuration store.
|
||||
virtual t_config_option_keys keys() const = 0;
|
||||
// Set this config's options on target member by member, when target is of this config's static type or
|
||||
// derives from it, and return true. apply() prefers this to looking every key up by name.
|
||||
virtual bool apply_to(ConfigBase &/*target*/) const { return false; }
|
||||
|
||||
protected:
|
||||
// Verify whether the opt_key has not been obsoleted or renamed.
|
||||
@@ -2753,7 +2756,8 @@ public:
|
||||
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
|
||||
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply_only(other, other.keys(), ignore_nonexistent); }
|
||||
void apply(const ConfigBase &other, bool ignore_nonexistent = false)
|
||||
{ if (! other.apply_to(*this)) this->apply_only(other, other.keys(), ignore_nonexistent); }
|
||||
// Apply explicitely enumerated keys of other ConfigBase defined by this->def() to this ConfigBase.
|
||||
// An UnknownOptionException is thrown in case some option keys are not defined by this->def(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include "FillBase.hpp"
|
||||
#include "FillGyroid.hpp"
|
||||
|
||||
@@ -123,44 +124,41 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool
|
||||
}
|
||||
}
|
||||
|
||||
// Repeats one period of a wave from the last sample at or before x_min to the first one at or after x_max.
|
||||
static inline Polyline make_wave(
|
||||
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
|
||||
double z_cos, double z_sin, bool vertical, bool flip)
|
||||
const std::vector<Vec2d>& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical)
|
||||
{
|
||||
std::vector<Vec2d> points = one_period;
|
||||
double period = points.back()(0);
|
||||
if (width != period) // do not extend if already truncated
|
||||
{
|
||||
points.reserve(one_period.size() * size_t(floor(width / period)));
|
||||
points.pop_back();
|
||||
const double period = one_period.back().x();
|
||||
// The last sample of a period is the first one of the next.
|
||||
const size_t n = one_period.size() - 1;
|
||||
double x0 = std::floor(x_min / period) * period;
|
||||
size_t i = 0;
|
||||
while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min)
|
||||
++i;
|
||||
|
||||
size_t n = points.size();
|
||||
do {
|
||||
points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y());
|
||||
} while (points.back()(0) < width - EPSILON);
|
||||
|
||||
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
|
||||
// and construct the final polyline to return:
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(points.size());
|
||||
for (auto& point : points) {
|
||||
point(1) += offset;
|
||||
point(1) = std::clamp(double(point.y()), 0., height);
|
||||
polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1);
|
||||
for (;;) {
|
||||
Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset);
|
||||
const bool last = point.x() >= x_max;
|
||||
if (vertical)
|
||||
std::swap(point(0), point(1));
|
||||
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
|
||||
if (last)
|
||||
break;
|
||||
if (++i == n) {
|
||||
i = 0;
|
||||
x0 += period;
|
||||
}
|
||||
}
|
||||
|
||||
return polyline;
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
static std::vector<Vec2d> make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
{
|
||||
std::vector<Vec2d> points;
|
||||
double dx = M_PI_2; // exact coordinates on main inflexion lobes
|
||||
double limit = std::min(2*M_PI, width);
|
||||
double limit = 2*M_PI;
|
||||
points.reserve(coord_t(ceil(limit / tolerance / 3)));
|
||||
|
||||
for (double x = 0.; x < limit - EPSILON; x += dx) {
|
||||
@@ -239,7 +237,8 @@ static inline double compute_omega_factor(double density_adjusted, double line_s
|
||||
return std::clamp(raw, 1.0, 2.0);
|
||||
}
|
||||
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
||||
// Waves covering bbox, with the pattern anchored at origin.
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, const BoundingBox &bbox, const Point &origin)
|
||||
{
|
||||
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
||||
|
||||
@@ -254,29 +253,36 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
|
||||
const double z_cos = cos(z);
|
||||
|
||||
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
|
||||
// Range to cover in pattern units, with the waves running along x.
|
||||
Vec2d lo = (bbox.min - origin).cast<double>() / scaleFactor;
|
||||
Vec2d hi = (bbox.max - origin).cast<double>() / scaleFactor;
|
||||
double lower_bound = 0.;
|
||||
double upper_bound = height;
|
||||
bool flip = true;
|
||||
if (vertical) {
|
||||
flip = false;
|
||||
lower_bound = -M_PI;
|
||||
upper_bound = width - M_PI_2;
|
||||
std::swap(width,height);
|
||||
std::swap(lo(0), lo(1));
|
||||
std::swap(hi(0), hi(1));
|
||||
}
|
||||
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
flip = !flip; // even polylines are a bit shifted
|
||||
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
Polylines result;
|
||||
std::vector<Vec2d> one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
|
||||
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
|
||||
// creates odd polylines
|
||||
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// creates even polylines
|
||||
y0 += M_PI;
|
||||
if (y0 < upper_bound + EPSILON) {
|
||||
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// Every wave spans [offset + f_min, offset + f_max] across.
|
||||
double f_min = std::numeric_limits<double>::max();
|
||||
double f_max = std::numeric_limits<double>::lowest();
|
||||
for (const std::vector<Vec2d> *one_period : { &one_period_odd, &one_period_even })
|
||||
for (const Vec2d &point : *one_period) {
|
||||
f_min = std::min(f_min, point.y());
|
||||
f_max = std::max(f_max, point.y());
|
||||
}
|
||||
|
||||
Polylines result;
|
||||
for (int i = int(std::ceil((lo.y() - f_max - lower_bound) / M_PI)); lower_bound + i * M_PI + f_min <= hi.y(); ++i) {
|
||||
Polyline &wave = result.emplace_back(make_wave(i % 2 == 0 ? one_period_odd : one_period_even, lo.x(), hi.x(),
|
||||
lower_bound + i * M_PI, scaleFactor, vertical));
|
||||
wave.translate(origin);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -302,12 +308,12 @@ void FillGyroid::_fill_surface_single(
|
||||
// Distance between the gyroid waves in scaled coordinates.
|
||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||
|
||||
// align bounding box to a multiple of our grid module
|
||||
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
||||
// Anchor the pattern to our grid module; the 10-line shift keeps its established phase.
|
||||
const coord_t shift = coord_t(10 * scale_(this->spacing));
|
||||
const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift);
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
coord_t expand = 10 * (scale_(this->spacing));
|
||||
bb.offset(expand);
|
||||
// Keep the pattern ends and the multiline copies outside the contour.
|
||||
bb.offset(scale_(this->spacing * params.multiline));
|
||||
|
||||
// generate pattern
|
||||
Polylines polylines;
|
||||
@@ -327,23 +333,14 @@ void FillGyroid::_fill_surface_single(
|
||||
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
|
||||
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
|
||||
|
||||
// bb is already expanded above by 10 * scale_(spacing) for edge artifacts;
|
||||
// skip a second offset here to avoid raster-area bloat in the marching squares pass.
|
||||
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
|
||||
const coord_t cell = scaled(marchsq::GyroidField::gsizef);
|
||||
bb.offset(cell);
|
||||
bb.merge(align_to_grid(bb.min, Point(cell, cell)));
|
||||
marchsq::GyroidField sf(bb, this->z, period, float(omega));
|
||||
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
|
||||
} else {
|
||||
polylines = make_gyroid_waves(
|
||||
scale_(this->z),
|
||||
density_adjusted,
|
||||
this->spacing,
|
||||
ceil(bb.size()(0) / distance) + 1.,
|
||||
ceil(bb.size()(1) / distance) + 1.);
|
||||
|
||||
// The parametric generator produces wave coords relative to the grid origin;
|
||||
// shift them into absolute layer coords. The marching-squares branch above
|
||||
// already emits absolute coords via GyroidField::to_Point, so it skips this.
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin);
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
|
||||
+50
-32
@@ -976,6 +976,37 @@ void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_
|
||||
}
|
||||
}
|
||||
|
||||
DynamicPrintConfig Preset::load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
|
||||
const std::set<std::string> &different_settings_list,
|
||||
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
|
||||
t_config_option_keys *keys)
|
||||
{
|
||||
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
|
||||
DynamicPrintConfig cfg(default_config);
|
||||
// SoftFever: ignore print connection info from project
|
||||
auto cfg_keys = cfg.keys();
|
||||
cfg_keys.erase(std::remove_if(cfg_keys.begin(), cfg_keys.end(),
|
||||
[](std::string &val) {
|
||||
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
|
||||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
|
||||
}),
|
||||
cfg_keys.end());
|
||||
cfg.apply_only(project_config, cfg_keys, true);
|
||||
|
||||
//add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
if (!different_settings_list.empty()) {
|
||||
if (DynamicPrintConfig *base_config = find_base(Preset::inherits(cfg))) {
|
||||
std::string extruder_id_name, extruder_variant_name;
|
||||
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
|
||||
Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
|
||||
cfg.update_non_diff_values_to_base_config(*base_config, cfg_keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
}
|
||||
if (keys)
|
||||
*keys = std::move(cfg_keys);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool Preset::has_lidar(PresetBundle *preset_bundle)
|
||||
{
|
||||
bool has_lidar = false;
|
||||
@@ -2648,20 +2679,6 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
const Semver file_version,
|
||||
const std::string filament_id)
|
||||
{
|
||||
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
|
||||
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
|
||||
// SoftFever: ignore print connection info from project
|
||||
auto keys = cfg.keys();
|
||||
keys.erase(std::remove_if(keys.begin(), keys.end(),
|
||||
[](std::string &val) {
|
||||
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
|
||||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
|
||||
}),
|
||||
keys.end());
|
||||
cfg.apply_only(combined_config, keys, true);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
||||
//add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
|
||||
bool found = it != m_presets.end() && it->name == original_name;
|
||||
if (! found) {
|
||||
@@ -2670,24 +2687,25 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
found = it != m_presets.end();
|
||||
}
|
||||
|
||||
std::string extruder_id_name, extruder_variant_name;
|
||||
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
|
||||
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
|
||||
|
||||
if (!inherits.empty() && (different_settings_list.size() > 0)) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter != m_presets.end()) {
|
||||
//std::vector<std::string> dirty_options = cfg.diff(iter->config);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
}
|
||||
else if (found && it->is_system && (different_settings_list.size() > 0)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
|
||||
cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
t_config_option_keys keys;
|
||||
DynamicPrintConfig cfg = Preset::load_external_config(m_type, this->default_preset_for(combined_config).config, combined_config, different_settings_list,
|
||||
[this, &original_name, &path, found, it](const std::string &inherits) -> DynamicPrintConfig * {
|
||||
if (!inherits.empty()) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter == m_presets.end())
|
||||
return nullptr;
|
||||
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
return &iter->config;
|
||||
}
|
||||
if (found && it->is_system) {
|
||||
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
|
||||
return &it->config;
|
||||
}
|
||||
return nullptr;
|
||||
}, &keys);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits;
|
||||
|
||||
@@ -403,6 +403,15 @@ public:
|
||||
std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type
|
||||
|
||||
static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set<std::string>** p_key_set1, std::set<std::string>** p_key_set2);
|
||||
// Config of a preset loaded from a project or config file: the project's values over the type's
|
||||
// default preset config, without the print-host keys. When different_settings_list is not empty,
|
||||
// every key not listed in it is then refreshed from the base system preset, which find_base returns
|
||||
// for the project's "inherits" (nullptr when there is none), with the listed per-variant values
|
||||
// mapped onto the base's extruder variants. keys, if given, receives the keys taken from the project.
|
||||
static DynamicPrintConfig load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
|
||||
const std::set<std::string> &different_settings_list,
|
||||
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
|
||||
t_config_option_keys *keys = nullptr);
|
||||
std::string get_printer_id() const { return vendor ? vendor->id : ""; }
|
||||
|
||||
bool has_lidar(PresetBundle *preset_bundle);
|
||||
|
||||
@@ -589,18 +589,20 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
||||
const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error)
|
||||
std::string &error, bool allow_cache)
|
||||
{
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule);
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule, allow_cache);
|
||||
if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end())
|
||||
return it->second.get();
|
||||
|
||||
// The library loads with no base of its own, so the tree a vendor inherits from
|
||||
// is the same one that resolves the library's own presets.
|
||||
const PresetBundle *library = nullptr;
|
||||
const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
|
||||
const PresetBundle *library = nullptr;
|
||||
if (vendor_id != ORCA_FILAMENT_LIBRARY &&
|
||||
boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) {
|
||||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error);
|
||||
(boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) ||
|
||||
(allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) {
|
||||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache);
|
||||
if (library == nullptr) {
|
||||
error = "OrcaFilamentLibrary contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -609,7 +611,7 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
|
||||
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
bundle->m_preserve_vendor_source_paths = true;
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false);
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, allow_cache);
|
||||
if (bundle->error_count() != 0) {
|
||||
error = "Vendor bundle contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -652,6 +654,43 @@ bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresetBundle::resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error)
|
||||
{
|
||||
const std::string vendor_id = find_preset_vendor(name, type);
|
||||
if (vendor_id.empty()) {
|
||||
error = "No vendor lists the preset";
|
||||
return false;
|
||||
}
|
||||
// Release builds ship a vendor as its preset cache alone, without the profile JSONs.
|
||||
auto installed = [&vendor_id](const fs::path &root) {
|
||||
return fs::is_regular_file(root / (vendor_id + ".json")) || fs::is_regular_file(root / (vendor_id + ".opc"));
|
||||
};
|
||||
fs::path root_dir = fs::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||
if (!installed(root_dir))
|
||||
root_dir = fs::path(resources_dir()) / PRESET_PROFILES_DIR;
|
||||
const bool cache_only = !fs::is_regular_file(root_dir / (vendor_id + ".json"));
|
||||
|
||||
try {
|
||||
const PresetBundle *vendor = load_source_vendor(root_dir, vendor_id, compatibility_rule, error, cache_only);
|
||||
if (vendor == nullptr)
|
||||
return false;
|
||||
const PresetCollection &collection = type == Preset::TYPE_PRINTER ? vendor->printers :
|
||||
type == Preset::TYPE_PRINT ? vendor->prints : vendor->filaments;
|
||||
const Preset *preset = collection.find_preset(name, false);
|
||||
if (preset == nullptr) {
|
||||
error = "Preset was not found in its vendor bundle";
|
||||
return false;
|
||||
}
|
||||
config = preset->config;
|
||||
} catch (const std::exception &ex) {
|
||||
error = ex.what();
|
||||
return false;
|
||||
}
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle(const PresetBundle &rhs)
|
||||
{
|
||||
*this = rhs;
|
||||
@@ -3923,7 +3962,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
|
||||
// Snapshot and temporarily strip mixed filament slots so AMS sync operates on physical
|
||||
// filaments only. A mixed slot is virtual and has no tray to sync against; leaving it in
|
||||
// would let AMS mapping overwrite it and would break the physical-first slot ordering the
|
||||
@@ -4673,6 +4711,15 @@ const std::set<std::string> ignore_settings_list ={
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id"
|
||||
};
|
||||
|
||||
std::set<std::string> PresetBundle::project_different_keys(const std::string &different_settings)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
Slic3r::unescape_strings_cstyle(different_settings, keys);
|
||||
std::set<std::string> keys_set(keys.begin(), keys.end());
|
||||
keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
return keys_set;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new, std::optional<std::vector<int>> filament_volume_maps_new) const
|
||||
{
|
||||
DynamicPrintConfig out;
|
||||
@@ -5380,15 +5427,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
if (!is_published) {
|
||||
//BBS: add different settings logic
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load print preset from print_settings_id");
|
||||
std::vector<std::string> print_different_keys_vector;
|
||||
std::string print_different_settings = different_values[0];
|
||||
Slic3r::unescape_strings_cstyle(print_different_settings, print_different_keys_vector);
|
||||
std::set<std::string> print_different_keys_set(print_different_keys_vector.begin(), print_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// print_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
print_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> print_different_keys_set = project_different_keys(different_values[0]);
|
||||
if (!print_compatible_printers.empty()) {
|
||||
ConfigOptionStrings* compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true);
|
||||
compatible_printers->values = print_compatible_printers;
|
||||
@@ -5399,15 +5438,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
//clear compatible printers
|
||||
clear_compatible_printers(config);
|
||||
|
||||
std::vector<std::string> printer_different_keys_vector;
|
||||
std::string printer_different_settings = different_values[num_filaments + 1];
|
||||
Slic3r::unescape_strings_cstyle(printer_different_settings, printer_different_keys_vector);
|
||||
std::set<std::string> printer_different_keys_set(printer_different_keys_vector.begin(), printer_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// printer_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
printer_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> printer_different_keys_set = project_different_keys(different_values[num_filaments + 1]);
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load printer preset from printer_settings_id");
|
||||
load_preset(this->printers, num_filaments + 1, "printer_settings_id", printer_different_keys_set, std::string());
|
||||
@@ -5426,15 +5457,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
Preset *loaded = nullptr;
|
||||
|
||||
//BBS: add different settings logic
|
||||
std::vector<std::string> filament_different_keys_vector;
|
||||
std::string filament_different_settings = different_values[1];
|
||||
Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector);
|
||||
std::set<std::string> filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// filament_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> filament_different_keys_set = project_different_keys(different_values[1]);
|
||||
|
||||
std::string filament_id = filament_ids[0];
|
||||
//BBS: add config related logs
|
||||
@@ -5497,15 +5520,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
cfg.opt_string("inherits", true) = inherits_values[i + 1];
|
||||
|
||||
//BBS: add different settings logic
|
||||
std::vector<std::string> filament_different_keys_vector;
|
||||
std::string filament_different_settings = different_values[i+1];
|
||||
Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector);
|
||||
std::set<std::string> filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// filament_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> filament_different_keys_set = project_different_keys(different_values[i+1]);
|
||||
|
||||
std::string filament_id = filament_ids[i];
|
||||
|
||||
|
||||
@@ -235,6 +235,9 @@ public:
|
||||
|
||||
// ORCA: utility function to find the vendor for a given preset name
|
||||
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
|
||||
// Keys a project keeps when its presets are loaded: those listed in its escaped
|
||||
// "different_settings_to_system" entry for the preset, plus the preset bookkeeping keys.
|
||||
static std::set<std::string> project_different_keys(const std::string &different_settings);
|
||||
|
||||
PresetBundle();
|
||||
PresetBundle(const PresetBundle &rhs);
|
||||
@@ -274,6 +277,10 @@ public:
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest = true);
|
||||
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
|
||||
// there, as the GUI reads it, and from the bundled profiles otherwise.
|
||||
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
@@ -808,13 +815,14 @@ private:
|
||||
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset
|
||||
// resolved through this bundle shares one load per source root and vendor. The
|
||||
// filament library is one such tree, shared by every vendor under its root.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>>
|
||||
// A tree read from its preset cache is kept apart: its presets carry no source file.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
|
||||
m_source_vendor_bundles;
|
||||
|
||||
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error);
|
||||
std::string &error, bool allow_cache = false);
|
||||
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
|
||||
@@ -12128,6 +12128,21 @@ PRINT_CONFIG_CACHE_INITIALIZE((
|
||||
SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig))
|
||||
static int print_config_static_initialized = print_config_static_initializer();
|
||||
|
||||
// The same set() calls ConfigBase::apply_only() makes, without looking every key up by name. Out of line so the
|
||||
// option list is expanded for this once, not in every file that includes PrintConfig.hpp.
|
||||
#define PRINT_CONFIG_APPLY_TO_DEFINITION(r, data, CLASS_NAME) \
|
||||
bool CLASS_NAME::apply_to(ConfigBase &target) const \
|
||||
{ \
|
||||
auto *dst = dynamic_cast<CLASS_NAME*>(&target); \
|
||||
if (dst == nullptr) \
|
||||
return false; \
|
||||
visit_option_pairs(*dst, *this, [](const char*, ConfigOption &a, const ConfigOption &b) { a.set(&b); return true; }); \
|
||||
return true; \
|
||||
}
|
||||
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_APPLY_TO_DEFINITION, _, (PrintObjectConfig)(PrintRegionConfig)(MachineEnvelopeConfig)(GCodeConfig)
|
||||
(SLAMaterialConfig)(SLAPrintConfig)(SLAPrintObjectConfig)(SLAPrinterConfig))
|
||||
#undef PRINT_CONFIG_APPLY_TO_DEFINITION
|
||||
|
||||
//BBS: remove unused command currently
|
||||
CLIActionsConfigDef::CLIActionsConfigDef()
|
||||
{
|
||||
@@ -12765,18 +12780,10 @@ OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
|
||||
|
||||
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
|
||||
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
|
||||
|
||||
def = this->add("imex_mode", coString);
|
||||
def->label = L("IDEX/IQEX active mode");
|
||||
def->tooltip = L("Name of the active IDEX/IQEX parallel print mode for this plate (e.g. 'primary', 'mirror', 'copy'). Empty string if IDEX/IQEX is not enabled.");
|
||||
|
||||
def = this->add("imex_mode_index", coInt);
|
||||
def->label = L("IDEX/IQEX active mode index");
|
||||
def->tooltip = L("Zero-based index of the active IDEX/IQEX parallel print mode within imex_mode_names.");
|
||||
|
||||
def = this->add("imex_mode_gcode", coString);
|
||||
def->label = L("IDEX/IQEX active mode G-code");
|
||||
def->tooltip = L("The raw mode G-code template for the active IDEX/IQEX parallel print mode, after placeholder evaluation. Globals defined here flow into machine_start_gcode.");
|
||||
new_def("curr_bed_type", coString, "Current bed type", "Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
|
||||
new_def("imex_mode", coString, "IDEX/IQEX active mode", "Name of the active IDEX/IQEX parallel print mode for this plate (e.g. 'primary', 'mirror', 'copy'). Empty string if IDEX/IQEX is not enabled.");
|
||||
new_def("imex_mode_index", coInt, "IDEX/IQEX active mode index", "Zero-based index of the active IDEX/IQEX parallel print mode within imex_mode_names.");
|
||||
new_def("imex_mode_gcode", coString, "IDEX/IQEX active mode G-code", "The raw mode G-code template for the active IDEX/IQEX parallel print mode, after placeholder evaluation. Globals defined here flow into machine_start_gcode.");
|
||||
}
|
||||
|
||||
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
|
||||
|
||||
@@ -1053,9 +1053,10 @@ public: \
|
||||
|
||||
#define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem);
|
||||
#define PRINT_CONFIG_CLASS_ELEMENT_VISIT(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), this->BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return;
|
||||
#define PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), self.BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return;
|
||||
// Each option list is expanded into the members and again into for_each_option_pair(), which calls
|
||||
// f(key, this->option, rhs.option) in declaration order and stops when f returns false. hash(),
|
||||
// operator==, operator< and initialize() iterate the options through that visitor.
|
||||
// operator==, operator<, initialize() and apply_to() iterate the options through that visitor.
|
||||
#define PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \
|
||||
size_t hash() const throw() \
|
||||
{ \
|
||||
@@ -1087,11 +1088,16 @@ class CLASS_NAME : public StaticPrintConfig { \
|
||||
STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \
|
||||
public: \
|
||||
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \
|
||||
template<typename F> void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const \
|
||||
{ \
|
||||
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ) \
|
||||
} \
|
||||
template<typename F> void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { visit_option_pairs(*this, rhs, f); } \
|
||||
/* Defined in PrintConfig.cpp. */ \
|
||||
bool apply_to(ConfigBase &target) const override; \
|
||||
PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \
|
||||
private: \
|
||||
/* The one expansion of the option list, for a const self and for apply_to()'s mutable target. */ \
|
||||
template<typename Self, typename F> static void visit_option_pairs(Self &self, const CLASS_NAME &rhs, F &&f) \
|
||||
{ \
|
||||
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF, _, PARAMETER_DEFINITION_SEQ) \
|
||||
} \
|
||||
};
|
||||
|
||||
#define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem
|
||||
@@ -1112,6 +1118,8 @@ class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE)
|
||||
public: \
|
||||
PARAMETER_DEFINITION \
|
||||
template<typename F> void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { PARAMETER_VISIT } \
|
||||
/* Its parents each apply themselves to a target member by member, so this one keeps the lookup by name. */ \
|
||||
bool apply_to(ConfigBase &/*target*/) const override { return false; } \
|
||||
size_t hash() const throw() \
|
||||
{ \
|
||||
size_t seed = 0; \
|
||||
@@ -2222,6 +2230,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0(
|
||||
#undef STATIC_PRINT_CONFIG_CACHE_DERIVED
|
||||
#undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION
|
||||
#undef PRINT_CONFIG_CLASS_ELEMENT_VISIT
|
||||
#undef PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF
|
||||
#undef PRINT_CONFIG_CLASS_COMMON_BODY
|
||||
#undef PRINT_CONFIG_CLASS_DEFINE
|
||||
#undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST
|
||||
|
||||
@@ -236,6 +236,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/GLToolbar.hpp
|
||||
GUI/ImageDPIFrame.cpp
|
||||
GUI/ImageDPIFrame.hpp
|
||||
GUI/IMediaController.hpp
|
||||
GUI/GUI_App.cpp
|
||||
GUI/GUI_App.hpp
|
||||
GUI/GUI_AuxiliaryList.cpp
|
||||
@@ -369,6 +370,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/MediaFilePanel.h
|
||||
GUI/MediaPlayCtrl.cpp
|
||||
GUI/MediaPlayCtrl.h
|
||||
GUI/WebRtcMediaController.cpp
|
||||
GUI/WebRtcMediaController.hpp
|
||||
GUI/MeshUtils.cpp
|
||||
GUI/MeshUtils.hpp
|
||||
GUI/ModelMall.cpp
|
||||
@@ -564,6 +567,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/WebUserLoginDialog.hpp
|
||||
GUI/WebViewDialog.cpp
|
||||
GUI/WebViewDialog.hpp
|
||||
GUI/WebMediaController.hpp
|
||||
GUI/WebMediaController.cpp
|
||||
GUI/Widgets/AMSControl.cpp
|
||||
GUI/Widgets/AMSControl.hpp
|
||||
GUI/Widgets/AMSItem.cpp
|
||||
@@ -758,6 +763,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/NetworkAgentFactory.cpp
|
||||
Utils/ICloudServiceAgent.hpp
|
||||
Utils/IPrinterAgent.hpp
|
||||
Utils/ICameraSignalingChannel.hpp
|
||||
Utils/OrcaCloudServiceAgent.cpp
|
||||
Utils/OrcaCloudServiceAgent.hpp
|
||||
Utils/OrcaPrinterAgent.cpp
|
||||
@@ -928,7 +934,7 @@ else()
|
||||
set(_opengl_link_lib OpenGL::GL)
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed)
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto LibDataChannel::LibDataChannel noise::noise pybind11::embed)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Linux finds wxWidgets in module mode, whose include dirs and definitions
|
||||
@@ -985,29 +991,32 @@ endif ()
|
||||
|
||||
if (APPLE)
|
||||
# Static FFmpeg from the deps install: nothing to bundle into the .app,
|
||||
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
|
||||
# no rpath/install_name handling. Order matters: avformat -> avcodec -> swscale -> avutil.
|
||||
find_library(LIBAVFORMAT_LIBRARY NAMES libavformat.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
|
||||
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "Static FFmpeg (libavformat.a/libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
|
||||
endif ()
|
||||
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||
elseif (WIN32)
|
||||
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
|
||||
# so resolve the import libraries out of the deps prefix directly; the DLLs
|
||||
# are copied next to the executable by the top level CMakeLists.
|
||||
find_library(LIBAVFORMAT_LIBRARY NAMES avformat PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
|
||||
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "FFmpeg (avformat/avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
|
||||
endif ()
|
||||
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||
else ()
|
||||
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
|
||||
libavformat
|
||||
libavcodec
|
||||
libswscale
|
||||
libavutil
|
||||
|
||||
@@ -45,6 +45,25 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::open(AVCodecParameters const ¶meters)
|
||||
{
|
||||
if (avcodec_parameters_to_context(codec_ctx_, ¶meters) < 0)
|
||||
return -1;
|
||||
|
||||
auto codec = avcodec_find_decoder(codec_ctx_->codec_id);
|
||||
if (codec == nullptr) {
|
||||
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
|
||||
return -1;
|
||||
}
|
||||
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
|
||||
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_ = av_frame_alloc();
|
||||
return frame_ == nullptr ? -1 : 0;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::decode(const Bambu_Sample &sample)
|
||||
{
|
||||
int ret = -1;
|
||||
@@ -71,6 +90,14 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::decode(const AVPacket &packet)
|
||||
{
|
||||
int ret = avcodec_send_packet(codec_ctx_, &packet);
|
||||
if (ret == 0)
|
||||
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::flush()
|
||||
{
|
||||
int ret = avcodec_send_packet(codec_ctx_, nullptr);
|
||||
|
||||
@@ -23,8 +23,10 @@ public:
|
||||
|
||||
public:
|
||||
int open(Bambu_StreamInfo const &info);
|
||||
int open(AVCodecParameters const ¶meters);
|
||||
|
||||
int decode(Bambu_Sample const &sample);
|
||||
int decode(AVPacket const &packet);
|
||||
|
||||
int flush();
|
||||
|
||||
@@ -34,6 +36,14 @@ public:
|
||||
|
||||
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
|
||||
|
||||
// Native size of the most recently decoded frame, or an unspecified size if
|
||||
// nothing has decoded yet. Lets a caller learn the video dimensions when the
|
||||
// container/probe could not report them up front.
|
||||
wxSize decoded_frame_size() const
|
||||
{
|
||||
return got_frame_ && frame_ ? wxSize{frame_->width, frame_->height} : wxSize{};
|
||||
}
|
||||
|
||||
private:
|
||||
AVCodecContext *codec_ctx_ = nullptr;
|
||||
AVFrame * frame_ = nullptr;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// GENERATED FILE — DO NOT EDIT.
|
||||
// Source: docs/ux/tool_atlas.json Generator: docs/ux/mockups/gen_offer_table.py
|
||||
// Source: scripts/CAD/tool_atlas.json Generator: scripts/CAD/gen_offer_table.py
|
||||
//
|
||||
// The object-driven tool offer (charter 4.1): every verb has ONE row index, that index
|
||||
// is the same in every selection it appears in, and verbs that do not apply are shown
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "slic3r/GUI/CAD/DesignPanel.hpp"
|
||||
#include "slic3r/GUI/CAD/DesignCanvas.hpp"
|
||||
#include "slic3r/GUI/CAD/DesignSketchTool.hpp"
|
||||
#include "slic3r/GUI/CAD/DesignOffer.hpp" // generated offer table — see docs/ux/tool_atlas.json
|
||||
#include "slic3r/GUI/CAD/DesignOffer.hpp" // generated offer table — see scripts/CAD/tool_atlas.json
|
||||
#include "libslic3r/CAD/GeometryEngine.hpp" // face_by_index for face-extrude gizmo anchor
|
||||
#include "libslic3r/TriangleMesh.hpp" // mesh import: STL/OBJ -> indexed_triangle_set
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
@@ -6067,7 +6067,7 @@ bool DesignPanel::sketch_plane_target(wxString& what) const
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The object-driven offer (charter §4.1). Right-click the geometry and get a vertical list in the
|
||||
// ratified row order, with the verbs that do not apply DISABLED IN PLACE carrying their reason.
|
||||
// The rows come from DesignOffer.hpp, generated from docs/ux/tool_atlas.json — the same file the
|
||||
// The rows come from DesignOffer.hpp, generated from scripts/CAD/tool_atlas.json — the same file the
|
||||
// mockups are drawn from, so a drawing and the product cannot drift apart.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -4034,7 +4034,7 @@ void DesignSketchTool::render_mate_connectors()
|
||||
// the information. The same bear rendered flat vs in relief gives 164 vs 210 lit pixels at 16
|
||||
// degrees and 66 vs 120 at 6. So the glyph is a small shaded solid, not an outline.
|
||||
//
|
||||
// The geometry is EMITTED from the real part by doc/design/mate-connectors/emit_glyph_table.py,
|
||||
// The geometry is EMITTED from the real part by scripts/CAD/mate-glyph/emit_glyph_table.py,
|
||||
// not hand-drawn, so the glyph and the printed connector cannot drift apart. Two vertices stand
|
||||
// above the 3 mm plate in the actual B-rep, which is why the snout here is a tent with one crest
|
||||
// edge and four flanks drafted at 20 degrees rather than anything more elaborate.
|
||||
@@ -4042,7 +4042,7 @@ void DesignSketchTool::render_mate_connectors()
|
||||
// The cheek dot is the handedness mark. Without it the glyph differs from its own mirror by only
|
||||
// 5-9 % of its lit pixels, which is not enough to read; the dot roughly doubles that and, unlike
|
||||
// making the eyes uneven, identifies the side from that cheek alone instead of by comparison.
|
||||
// Emitted by doc/design/mate-connectors/emit_glyph_table.py from bear.step — do not hand-edit.
|
||||
// Emitted by scripts/CAD/mate-glyph/emit_glyph_table.py from bear.step — do not hand-edit.
|
||||
// Normalised to the part's bounding span and centred: the renderer scales by one radius.
|
||||
static const Vec2d kBearOutline[] = { // 12 verts, RDP eps 0.030, CCW
|
||||
{+0.3842, +0.3294}, {+0.3156, +0.4002}, {+0.2424, +0.3294},
|
||||
|
||||
@@ -328,7 +328,7 @@ void SelectMObjectPopup::update_user_devices()
|
||||
}
|
||||
|
||||
m_bind_machine_list.clear();
|
||||
m_bind_machine_list = dev->get_my_machine_list();
|
||||
m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
|
||||
//sort list
|
||||
std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "PrePrintChecker.hpp"
|
||||
|
||||
#include "DeviceCore/DevConfig.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevFilaBlackList.h"
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
@@ -1637,29 +1639,6 @@ void CalibrationPresetPage::update_combobox_filaments(MachineObject* obj)
|
||||
select_default_compatible_filament();
|
||||
}
|
||||
|
||||
bool CalibrationPresetPage::is_blocking_printing()
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
|
||||
MachineObject* obj_ = dev->get_selected_machine();
|
||||
if (obj_ == nullptr) return true;
|
||||
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
auto target_model = obj_->printer_type;
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CalibrationPresetPage::is_nozzle_info_synced() const
|
||||
{
|
||||
if (!curr_obj || !curr_obj->is_info_ready())
|
||||
@@ -1741,11 +1720,13 @@ void CalibrationPresetPage::update_show_status()
|
||||
}
|
||||
}
|
||||
|
||||
//if (is_blocking_printing()) {
|
||||
// show_status(CaliPresetPageStatus::CaliPresetStatusUnsupportedPrinter);
|
||||
// return;
|
||||
//}
|
||||
//else
|
||||
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
|
||||
if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) {
|
||||
has_optional_printer_model = has_optional_printer_model ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(
|
||||
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle));
|
||||
}
|
||||
|
||||
if (obj_->is_connecting() || !obj_->is_connected()) {
|
||||
show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting);
|
||||
return;
|
||||
@@ -1797,7 +1778,9 @@ void CalibrationPresetPage::update_show_status()
|
||||
return;
|
||||
}
|
||||
|
||||
show_status(CaliPresetPageStatus::CaliPresetStatusNormal);
|
||||
show_status(has_optional_printer_model ?
|
||||
CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel :
|
||||
CaliPresetPageStatus::CaliPresetStatusNormal);
|
||||
}
|
||||
|
||||
|
||||
@@ -1851,6 +1834,10 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status)
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
else if (status == CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel) {
|
||||
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true);
|
||||
Enable_Send_Button(true);
|
||||
}
|
||||
else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) {
|
||||
wxString msg_text = _L("No login account, only printers in LAN mode are displayed.");
|
||||
update_print_status_msg(msg_text, false);
|
||||
|
||||
@@ -152,7 +152,8 @@ enum CaliPresetPageStatus
|
||||
CaliPresetStatusInConnecting,
|
||||
CaliPresetStatusFilamentIncompatible,
|
||||
CaliPresetStatusLanModeSDcardNotAvailable,
|
||||
CaliPresetStatusDifferentNozzleDiameters
|
||||
CaliPresetStatusDifferentNozzleDiameters,
|
||||
CaliPresetStatusOptionalPrinterModel
|
||||
};
|
||||
|
||||
class CalibrationPresetPage : public CalibrationWizardPage
|
||||
@@ -268,7 +269,6 @@ protected:
|
||||
bool is_nozzle_info_synced() const;
|
||||
void show_status(CaliPresetPageStatus status);
|
||||
void Enable_Send_Button(bool enable);
|
||||
bool is_blocking_printing();
|
||||
bool need_check_sdcard(MachineObject* obj);
|
||||
|
||||
CaliPresetPageStatus get_status() { return m_page_status; }
|
||||
|
||||
@@ -28,7 +28,6 @@ wxEND_EVENT_TABLE()
|
||||
|
||||
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
||||
|
||||
#define CAMERAPOPUP_CLICK_INTERVAL 20
|
||||
|
||||
@@ -102,34 +101,6 @@ CameraPopup::CameraPopup(wxWindow *parent)
|
||||
top_sizer->Add(0, 0, wxALL, 0);
|
||||
}
|
||||
|
||||
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
|
||||
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
|
||||
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
|
||||
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
|
||||
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
|
||||
m_custom_camera_input_confirm->SetFont(Label::Body_14);
|
||||
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
|
||||
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
|
||||
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
|
||||
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
|
||||
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
|
||||
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
|
||||
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
|
||||
m_custom_camera_hint->Wrap(-1);
|
||||
m_custom_camera_hint->SetFont(Label::Head_14);
|
||||
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
|
||||
|
||||
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
|
||||
|
||||
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
|
||||
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
|
||||
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
|
||||
}
|
||||
|
||||
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
|
||||
top_sizer->Add(0, 0, wxALL, 0);
|
||||
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, FromDIP(5));
|
||||
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
|
||||
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
|
||||
|
||||
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
|
||||
@@ -184,37 +155,6 @@ void CameraPopup::sdcard_absent_hint()
|
||||
GetEventHandler()->ProcessEvent(evt);
|
||||
}
|
||||
|
||||
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
|
||||
{
|
||||
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
|
||||
handle_camera_source_change();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraPopup::handle_camera_source_change()
|
||||
{
|
||||
m_custom_camera_enabled = !m_custom_camera_enabled;
|
||||
|
||||
set_custom_cam_button_state(m_custom_camera_enabled);
|
||||
|
||||
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
|
||||
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
|
||||
|
||||
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
|
||||
evt.SetEventObject(this);
|
||||
GetEventHandler()->ProcessEvent(evt);
|
||||
}
|
||||
|
||||
void CameraPopup::set_custom_cam_button_state(bool state)
|
||||
{
|
||||
m_custom_camera_enabled = state;
|
||||
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
|
||||
auto stateText = state ? "Disable" : "Enable";
|
||||
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
|
||||
m_custom_camera_input_confirm->SetBorderColor(stateColour);
|
||||
m_custom_camera_input_confirm->SetLabel(_L(stateText));
|
||||
}
|
||||
|
||||
void CameraPopup::on_switch_recording(wxCommandEvent& event)
|
||||
{
|
||||
if (!m_obj) return;
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
#include "Widgets/SwitchButton.hpp"
|
||||
#include "Widgets/RadioBox.hpp"
|
||||
#include "Widgets/PopupWindow.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
||||
|
||||
class CameraPopup : public PopupWindow
|
||||
{
|
||||
@@ -53,9 +51,6 @@ protected:
|
||||
void on_switch_recording(wxCommandEvent& event);
|
||||
void on_set_resolution();
|
||||
void sdcard_absent_hint();
|
||||
void on_camera_source_changed(wxCommandEvent& event);
|
||||
void handle_camera_source_change();
|
||||
void set_custom_cam_button_state(bool state);
|
||||
|
||||
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
|
||||
void select_curr_radiobox(int btn_idx);
|
||||
@@ -76,10 +71,10 @@ private:
|
||||
wxStaticText* m_text_liveview_retry;
|
||||
SwitchButton* m_switch_liveview_retry;
|
||||
#endif //BBL_RELEASE_TO_PUBLIC
|
||||
wxStaticText* m_custom_camera_hint;
|
||||
TextInput* m_custom_camera_input;
|
||||
Button* m_custom_camera_input_confirm;
|
||||
bool m_custom_camera_enabled{ false };
|
||||
// wxStaticText* m_custom_camera_hint;
|
||||
// TextInput* m_custom_camera_input;
|
||||
// Button* m_custom_camera_input_confirm;
|
||||
// bool m_custom_camera_enabled{ false };
|
||||
wxStaticText* m_text_resolution;
|
||||
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
|
||||
wxScrolledWindow *m_panel;
|
||||
|
||||
@@ -35,7 +35,9 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
|
||||
sizer_connect = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_textCtrl_code = new TextInput(this, wxEmptyString);
|
||||
m_textCtrl_code->GetTextCtrl()->SetMaxLength(10);
|
||||
// OrcaSonar uses a 12-character base32 access code. Keep this field long
|
||||
// enough for it while retaining the existing validation for LAN codes.
|
||||
m_textCtrl_code->GetTextCtrl()->SetMaxLength(12);
|
||||
m_textCtrl_code->SetFont(Label::Body_14);
|
||||
m_textCtrl_code->SetCornerRadius(FromDIP(5));
|
||||
m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40)));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "DevConfigUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
|
||||
#include <wx/dir.h>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include "../I18N.hpp"
|
||||
@@ -41,6 +43,19 @@ static void _toolhead_translation_markers()
|
||||
|
||||
std::string DevPrinterConfigUtil::m_resource_file_path = "";
|
||||
|
||||
bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine)
|
||||
{
|
||||
const std::string& target_model = machine.printer_type;
|
||||
if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model))
|
||||
return true;
|
||||
|
||||
if (source_model == target_model)
|
||||
return true;
|
||||
|
||||
const auto compatible_machine = machine.get_compatible_machine();
|
||||
return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end();
|
||||
}
|
||||
|
||||
|
||||
std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name()
|
||||
{
|
||||
@@ -405,4 +420,4 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
class MachineObject;
|
||||
|
||||
/// Toolhead component type (extruder / nozzle / hotend)
|
||||
enum class ToolHeadComponent {
|
||||
Extruder,
|
||||
@@ -59,6 +61,10 @@ public:
|
||||
/*printer*/
|
||||
// info
|
||||
static std::map<std::string, std::string> get_all_model_id_with_name();
|
||||
// A printer agent may not know the physical model. Keep that case optional so
|
||||
// model compatibility checks do not turn missing identity into a hard error.
|
||||
static bool is_printer_model_compatible(const std::string& source_model, MachineObject& machine);
|
||||
static bool is_optional_printer_model_id(const std::string& model_id) { return model_id.empty(); }
|
||||
static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); }
|
||||
static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); }
|
||||
static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); }
|
||||
@@ -227,4 +233,4 @@ static std::string _parse_printer_type(const std::string &type_str)
|
||||
return type_str;
|
||||
}
|
||||
|
||||
};// namespace Slic3r
|
||||
};// namespace Slic3r
|
||||
|
||||
@@ -762,9 +762,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->remain = -1;
|
||||
}
|
||||
if (tray_it->contains("tray_slot_placeholder")) {
|
||||
curr_tray->is_slot_placeholder = true;
|
||||
}
|
||||
// The tray objects are reused across status updates. Reset this
|
||||
// state when a previously empty slot receives a filament again.
|
||||
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
|
||||
int ams_id_int = 0;
|
||||
int tray_id_int = 0;
|
||||
try
|
||||
@@ -999,4 +999,4 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "DevManager.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "DevManager.h"
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include "CloudProvider.hpp"
|
||||
#include "DevUtil.h"
|
||||
|
||||
// TODO: remove this include
|
||||
@@ -15,6 +17,8 @@
|
||||
|
||||
#include "libslic3r/Time.hpp"
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace {
|
||||
@@ -46,10 +50,12 @@ namespace {
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
DeviceManager::DeviceManager(NetworkAgent* agent)
|
||||
DeviceManager::DeviceManager(NetworkAgent* agent, bool enable_refresher, AppConfig* app_config)
|
||||
{
|
||||
m_agent = agent;
|
||||
m_refresher = new DeviceManagerRefresher(this);
|
||||
m_agent = agent;
|
||||
m_app_config = app_config;
|
||||
if (enable_refresher)
|
||||
m_refresher = new DeviceManagerRefresher(this);
|
||||
|
||||
DevPrinterConfigUtil::InitFilePath(resources_dir());
|
||||
|
||||
@@ -60,9 +66,14 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
AppConfig* DeviceManager::get_app_config() const
|
||||
{
|
||||
return m_app_config ? m_app_config : GUI::wxGetApp().app_config;
|
||||
}
|
||||
|
||||
void DeviceManager::load_local_machines_from_config()
|
||||
{
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
AppConfig* config = get_app_config();
|
||||
if (!config)
|
||||
return;
|
||||
const auto local_machines = config->get_local_machines();
|
||||
@@ -88,9 +99,8 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::update_local_machine(const MachineObject& m)
|
||||
void DeviceManager::update_local_machine(const MachineObject& m, AppConfig* config)
|
||||
{
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
if (m.is_lan_mode_printer()) {
|
||||
if (m.has_access_right()) {
|
||||
@@ -111,7 +121,8 @@ namespace Slic3r
|
||||
|
||||
DeviceManager::~DeviceManager()
|
||||
{
|
||||
delete m_refresher;
|
||||
if (m_refresher)
|
||||
delete m_refresher;
|
||||
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
|
||||
{
|
||||
@@ -171,14 +182,22 @@ namespace Slic3r
|
||||
return printer_agent ? printer_agent->get_agent_info().id : "";
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_current_cloud_provider() const
|
||||
{
|
||||
const std::string agent_id = get_current_printer_agent_id();
|
||||
if (!agent_id.empty())
|
||||
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
|
||||
return GUI::wxGetApp().get_printer_cloud_provider();
|
||||
}
|
||||
|
||||
void DeviceManager::EnableMultiMachine(bool enable)
|
||||
{
|
||||
m_agent->enable_multi_machine(enable);
|
||||
m_enable_mutil_machine = enable;
|
||||
}
|
||||
|
||||
void DeviceManager::start_refresher() { m_refresher->Start(); }
|
||||
void DeviceManager::stop_refresher() { m_refresher->Stop(); }
|
||||
void DeviceManager::start_refresher() { if (m_refresher) m_refresher->Start(); }
|
||||
void DeviceManager::stop_refresher() { if (m_refresher) m_refresher->Stop(); }
|
||||
|
||||
|
||||
void DeviceManager::keep_alive()
|
||||
@@ -265,6 +284,10 @@ namespace Slic3r
|
||||
/* update userMachineList info */
|
||||
auto it = userMachineList.find(dev_id);
|
||||
if (it != userMachineList.end()) {
|
||||
// A reused entry may have been created while another printer agent was active.
|
||||
// The response was obtained through the current agent, so move ownership with
|
||||
// the entry; otherwise agent-scoped lists hide it after a preset switch.
|
||||
it->second->printer_agent_id = get_current_printer_agent_id();
|
||||
if (it->second->get_dev_ip() != dev_ip ||
|
||||
it->second->bind_state != bind_state ||
|
||||
it->second->bind_sec_link != sec_link ||
|
||||
@@ -291,10 +314,15 @@ namespace Slic3r
|
||||
|
||||
/* update localMachineList */
|
||||
it = localMachineList.find(dev_id);
|
||||
AppConfig* config = get_app_config();
|
||||
|
||||
if (it != localMachineList.end()) {
|
||||
// update properties
|
||||
/* ip changed */
|
||||
obj = it->second;
|
||||
// A reused LAN entry may have been discovered while another printer agent was
|
||||
// active. The current discovery message establishes ownership for this agent.
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
|
||||
if (obj->get_dev_ip().compare(dev_ip) != 0) {
|
||||
if ( connection_name.empty() ) {
|
||||
@@ -384,7 +412,6 @@ namespace Slic3r
|
||||
obj->m_is_online = true;
|
||||
|
||||
//load access code
|
||||
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false);
|
||||
}
|
||||
@@ -407,7 +434,7 @@ namespace Slic3r
|
||||
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
|
||||
}
|
||||
}
|
||||
update_local_machine(*obj);
|
||||
update_local_machine(*obj, config);
|
||||
}
|
||||
catch (...) {
|
||||
;
|
||||
@@ -422,6 +449,9 @@ namespace Slic3r
|
||||
auto it = localMachineList.find(machine.dev_id);
|
||||
if (it != localMachineList.end()) {
|
||||
obj = it->second;
|
||||
// insert_local_device is called by the active agent, so a reused entry must follow
|
||||
// that agent as well; otherwise the agent-scoped printer list hides it.
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
} else {
|
||||
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
@@ -439,11 +469,16 @@ namespace Slic3r
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(access_code, false);
|
||||
|
||||
update_local_machine(*obj);
|
||||
update_local_machine(*obj, get_app_config());
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void DeviceManager::update_local_machine(const MachineObject& m)
|
||||
{
|
||||
update_local_machine(m, GUI::wxGetApp().app_config);
|
||||
}
|
||||
|
||||
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
|
||||
{
|
||||
if (!m_agent)
|
||||
@@ -514,7 +549,7 @@ namespace Slic3r
|
||||
|
||||
MachineObject* DeviceManager::get_my_machine(std::string dev_id)
|
||||
{
|
||||
auto list = get_my_machine_list();
|
||||
auto list = get_my_machine_list(get_current_printer_agent_id());
|
||||
auto it = list.find(dev_id);
|
||||
if (it != list.end())
|
||||
{
|
||||
@@ -551,25 +586,15 @@ namespace Slic3r
|
||||
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::clear_other_devices(const std::string& target_agent_id)
|
||||
void DeviceManager::clear_other_devices()
|
||||
{
|
||||
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
|
||||
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
|
||||
//
|
||||
// Also drop "My Devices" stamped by a different agent than the one we're swapping to
|
||||
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
|
||||
// at this point): otherwise a device first discovered under agent A survives every swap
|
||||
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
|
||||
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
|
||||
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
|
||||
// like any other fresh device.
|
||||
const auto my = get_my_machine_list();
|
||||
// Device entries are now scoped by printer_agent_id when they are presented. Keep
|
||||
// agent-owned discoveries across a switch so agents without automatic discovery (and
|
||||
// plugins whose devices have not received an access code yet) do not lose their list.
|
||||
// Entries without an owner are legacy/unscoped and cannot safely be shown.
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end();)
|
||||
{
|
||||
const bool is_my_device = my.find(it->first) != my.end();
|
||||
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
|
||||
it->second->printer_agent_id != target_agent_id;
|
||||
if (!is_my_device || agent_mismatch)
|
||||
if (!it->second || it->second->printer_agent_id.empty())
|
||||
{
|
||||
delete it->second;
|
||||
it = localMachineList.erase(it);
|
||||
@@ -585,8 +610,22 @@ namespace Slic3r
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
|
||||
<< " cur_selected=" << selected_machine;
|
||||
auto my_machine_list = get_my_machine_list();
|
||||
auto my_machine_list = get_my_machine_list(get_current_printer_agent_id());
|
||||
auto it = my_machine_list.find(dev_id);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set_selected_machine lookup dev_id=" << dev_id
|
||||
<< " found=" << (it != my_machine_list.end())
|
||||
<< " my_machine_count=" << my_machine_list.size()
|
||||
<< " current_agent=" << get_current_printer_agent_id()
|
||||
<< " provider=" << GUI::wxGetApp().get_printer_cloud_provider();
|
||||
if (it != my_machine_list.end() && it->second) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: target machine dev_id=" << it->second->get_dev_id()
|
||||
<< " printer_agent_id=" << it->second->printer_agent_id
|
||||
<< " connection_type=" << it->second->connection_type()
|
||||
<< " dev_connection_type=" << it->second->dev_connection_type;
|
||||
} else if (!dev_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: target machine was not found in the current agent's machine list";
|
||||
return false;
|
||||
}
|
||||
|
||||
// disconnect last if dev_id difference from previous one
|
||||
auto last_selected = my_machine_list.find(selected_machine);
|
||||
@@ -597,7 +636,9 @@ namespace Slic3r
|
||||
m_agent->disconnect_printer();
|
||||
}
|
||||
else if (last_selected->second->connection_type() == "cloud") {
|
||||
m_agent->set_user_selected_machine("");
|
||||
const int result = m_agent->set_user_selected_machine("");
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: cleared previous cloud selection dev_id="
|
||||
<< selected_machine << " result=" << result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,11 +673,7 @@ namespace Slic3r
|
||||
m_agent->disconnect_printer();
|
||||
it->second->reset();
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
|
||||
#else
|
||||
it->second->connect(it->second->local_use_ssl);
|
||||
#endif
|
||||
it->second->connect();
|
||||
it->second->set_lan_mode_connection_state(true);
|
||||
}
|
||||
}
|
||||
@@ -649,18 +686,16 @@ namespace Slic3r
|
||||
{
|
||||
// diff dev_id, cloud => set_user_selected_machine(new)
|
||||
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id;
|
||||
m_agent->set_user_selected_machine(dev_id);
|
||||
const int result = m_agent->set_user_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set new cloud selection dev_id="
|
||||
<< dev_id << " result=" << result;
|
||||
it->second->reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id;
|
||||
it->second->reset();
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
|
||||
#else
|
||||
it->second->connect(it->second->local_use_ssl);
|
||||
#endif
|
||||
it->second->connect();
|
||||
it->second->set_lan_mode_connection_state(true);
|
||||
}
|
||||
}
|
||||
@@ -677,6 +712,8 @@ namespace Slic3r
|
||||
|
||||
selected_machine = dev_id;
|
||||
record_user_last_machine(selected_machine);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: DeviceManager selection complete selected_machine="
|
||||
<< selected_machine;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -707,7 +744,9 @@ namespace Slic3r
|
||||
dev_list.push_back(it->first);
|
||||
BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first;
|
||||
}
|
||||
m_agent->add_subscribe(dev_list);
|
||||
const int result = m_agent->add_subscribe(dev_list);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: add_user_subscribe count=" << dev_list.size()
|
||||
<< " result=" << result;
|
||||
}
|
||||
|
||||
|
||||
@@ -720,7 +759,9 @@ namespace Slic3r
|
||||
dev_list.push_back(it->first);
|
||||
BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first;
|
||||
}
|
||||
m_agent->del_subscribe(dev_list);
|
||||
const int result = m_agent->del_subscribe(dev_list);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: del_user_subscribe count=" << dev_list.size()
|
||||
<< " result=" << result;
|
||||
}
|
||||
|
||||
void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list)
|
||||
@@ -839,7 +880,24 @@ namespace Slic3r
|
||||
try
|
||||
{
|
||||
json j = json::parse(body);
|
||||
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
|
||||
|
||||
const bool has_request_context = j.contains("provider") && j.contains("agent_id") && j.contains("generation");
|
||||
const std::string current_provider = get_current_cloud_provider();
|
||||
const std::string provider = j.contains("provider") ? j["provider"].get<std::string>()
|
||||
: current_provider;
|
||||
const std::string agent_id = j.contains("agent_id") ? j["agent_id"].get<std::string>()
|
||||
: get_current_printer_agent_id();
|
||||
const std::uint64_t generation = j.value("generation", std::uint64_t(0));
|
||||
|
||||
if (has_request_context &&
|
||||
(provider != current_provider ||
|
||||
agent_id != get_current_printer_agent_id() ||
|
||||
generation != (m_agent ? m_agent->get_user_machine_list_generation() : 0))) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": ignoring stale response provider="
|
||||
<< provider << " agent_id=" << agent_id
|
||||
<< " generation=" << generation;
|
||||
return;
|
||||
}
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
|
||||
@@ -862,11 +920,14 @@ namespace Slic3r
|
||||
/* update field */
|
||||
obj = iter->second;
|
||||
obj->set_dev_id(dev_id);
|
||||
// A device can be rediscovered by a different agent after a preset
|
||||
// switch while retaining the same MachineObject instance.
|
||||
obj->printer_agent_id = agent_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = new MachineObject(this, m_agent, "", "", "");
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
obj->printer_agent_id = agent_id;
|
||||
if (m_agent)
|
||||
{
|
||||
obj->set_bind_status(m_agent->get_user_name(provider));
|
||||
@@ -874,13 +935,20 @@ namespace Slic3r
|
||||
|
||||
if (obj->get_dev_ip().empty())
|
||||
{
|
||||
obj->get_dev_ip() = Slic3r::GUI::wxGetApp().app_config->get("ip_address", dev_id);
|
||||
if (AppConfig* config = get_app_config())
|
||||
obj->get_dev_ip() = config->get("ip_address", dev_id);
|
||||
}
|
||||
userMachineList.insert(std::make_pair(dev_id, obj));
|
||||
}
|
||||
|
||||
if (!obj) continue;
|
||||
|
||||
// Orca cloud printers are only ever delivered through this REST
|
||||
// account list; tag them so DeviceManager's cloud/lan branches
|
||||
// (subscribe + deselect in set_selected_machine) treat them right.
|
||||
if (provider == ORCA_CLOUD_PROVIDER)
|
||||
obj->dev_connection_type = "cloud";
|
||||
|
||||
if (!elem["dev_id"].is_null())
|
||||
obj->set_dev_id(elem["dev_id"].get<std::string>());
|
||||
if (!elem["dev_name"].is_null())
|
||||
@@ -912,6 +980,12 @@ namespace Slic3r
|
||||
acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end());
|
||||
obj->set_access_code(acc_code);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parsed cloud machine dev_id=" << dev_id
|
||||
<< " name=" << obj->get_dev_name()
|
||||
<< " agent_id=" << obj->printer_agent_id
|
||||
<< " connection_type=" << obj->connection_type()
|
||||
<< " online=" << obj->m_is_online;
|
||||
}
|
||||
|
||||
//remove MachineObject from userMachineList
|
||||
@@ -927,6 +1001,9 @@ namespace Slic3r
|
||||
iterat++;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parse_user_print_info complete provider=" << provider
|
||||
<< " parsed_count=" << new_list.size()
|
||||
<< " stored_count=" << userMachineList.size();
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
@@ -943,25 +1020,29 @@ namespace Slic3r
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = m_agent->get_user_print_info(&http_code, &body, provider);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: get_user_print_info provider=" << provider
|
||||
<< " result=" << result << " http_code=" << http_code
|
||||
<< " body_bytes=" << body.size();
|
||||
if (result == 0)
|
||||
{
|
||||
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
|
||||
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: queueing parse_user_print_info on UI thread";
|
||||
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::record_user_last_machine(const std::string& dev_id)
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
|
||||
if (AppConfig* config = get_app_config()) {
|
||||
config->set("user_last_selected_machine", dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_user_last_machine() const
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
|
||||
if (AppConfig* config = get_app_config()) {
|
||||
const auto& user_last_machine = config->get("user_last_selected_machine");
|
||||
if (!user_last_machine.empty()) {
|
||||
return user_last_machine;
|
||||
} else if (m_agent) {
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Slic3r
|
||||
struct BBLocalMachine;
|
||||
class MachineObject;
|
||||
class NetworkAgent;
|
||||
class AppConfig;
|
||||
|
||||
namespace GUI {
|
||||
class GUI_App;
|
||||
@@ -24,6 +25,7 @@ class DeviceManager
|
||||
friend class DeviceManagerRefresher;
|
||||
private:
|
||||
NetworkAgent* m_agent{ nullptr };
|
||||
AppConfig* m_app_config{ nullptr };
|
||||
DeviceManagerRefresher* m_refresher{ nullptr };
|
||||
|
||||
bool m_enable_mutil_machine = false;
|
||||
@@ -35,11 +37,13 @@ private:
|
||||
std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */
|
||||
|
||||
public:
|
||||
DeviceManager(NetworkAgent* agent = nullptr);
|
||||
DeviceManager(NetworkAgent* agent = nullptr, bool enable_refresher = true,
|
||||
AppConfig* app_config = nullptr);
|
||||
~DeviceManager();
|
||||
|
||||
public:
|
||||
NetworkAgent* get_agent() const { return m_agent; }
|
||||
AppConfig* get_app_config() const;
|
||||
void set_agent(NetworkAgent* agent);
|
||||
|
||||
void start_refresher();
|
||||
@@ -74,10 +78,9 @@ public:
|
||||
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
|
||||
void clean_user_info(bool keep_local_selection = false);
|
||||
|
||||
// target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check,
|
||||
// just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the
|
||||
// live one - this runs before the live agent is repointed.
|
||||
void clear_other_devices(const std::string& target_agent_id = "");
|
||||
// Retain agent-owned LAN discoveries across a switch; the active-agent list filter keeps
|
||||
// entries from other agents hidden while allowing them to reappear when switched back.
|
||||
void clear_other_devices();
|
||||
|
||||
void load_last_machine();
|
||||
void update_user_machine_list_info(const std::string& provider);
|
||||
@@ -122,6 +125,7 @@ private:
|
||||
|
||||
void keep_alive();
|
||||
void check_pushing();
|
||||
std::string get_current_cloud_provider() const;
|
||||
|
||||
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
|
||||
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
|
||||
@@ -134,14 +138,15 @@ public:
|
||||
std::string connection_type, std::string bind_state, std::string version,
|
||||
std::string access_code);
|
||||
static void update_local_machine(const MachineObject& m);
|
||||
static void update_local_machine(const MachineObject& m, AppConfig* config);
|
||||
};
|
||||
|
||||
class DeviceManagerRefresher : public wxObject
|
||||
{
|
||||
wxTimer* m_timer{ nullptr };
|
||||
int m_timer_interval_msec = 5000;
|
||||
wxTimer* m_timer{nullptr};
|
||||
int m_timer_interval_msec = 5000;
|
||||
|
||||
DeviceManager* m_manager{ nullptr };
|
||||
DeviceManager* m_manager{nullptr};
|
||||
|
||||
public:
|
||||
DeviceManagerRefresher(DeviceManager* manger);
|
||||
@@ -154,4 +159,4 @@ public:
|
||||
protected:
|
||||
virtual void on_timer(wxTimerEvent& event);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "ReleaseNote.hpp"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <charconv>
|
||||
#include <codecvt>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/typeof/typeof.hpp>
|
||||
@@ -54,6 +56,7 @@
|
||||
#include "DeviceCore/DevStatus.h"
|
||||
#include "DeviceCore/DevUpgrade.h"
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
|
||||
#define CALI_DEBUG
|
||||
#define MINUTE_30 1800000 //ms
|
||||
@@ -380,8 +383,22 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
|
||||
wxString MachineObject::get_printer_type_display_str() const
|
||||
{
|
||||
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
|
||||
|
||||
// Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles.
|
||||
// For any other profile, we can simply consult preset bundle if the model_id exists.
|
||||
if (display_name.empty()) {
|
||||
for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) {
|
||||
for (const auto& model : vendor.models) {
|
||||
if (printer_type == model.model_id)
|
||||
display_name = model.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!display_name.empty())
|
||||
return display_name;
|
||||
else if (printer_type == "orcasonar")
|
||||
return "OrcaSonar Printer";
|
||||
else
|
||||
return _L("Unknown");
|
||||
}
|
||||
@@ -468,7 +485,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
{
|
||||
this->access_code = code;
|
||||
if (only_refresh) {
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
if (is_lan_mode_printer()) {
|
||||
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
|
||||
@@ -481,7 +498,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
// fresh from the cloud API's current response, so there's no cross-agent leakage
|
||||
// risk to guard against there.
|
||||
if (!code.empty()) {
|
||||
DeviceManager::update_local_machine(*this);
|
||||
DeviceManager::update_local_machine(*this, config);
|
||||
} else {
|
||||
// Only patch an existing record's code - don't persist a brand-new
|
||||
// never-bound entry just because set_access_code("") was called on it.
|
||||
@@ -1348,7 +1365,6 @@ int MachineObject::command_get_access_code() {
|
||||
return this->publish_json(j);
|
||||
}
|
||||
|
||||
|
||||
int MachineObject::command_request_push_all(bool request_now)
|
||||
{
|
||||
auto curr_time = std::chrono::system_clock::now();
|
||||
@@ -1480,26 +1496,20 @@ int MachineObject::command_upgrade_module(std::string url, std::string module_ty
|
||||
|
||||
int MachineObject::command_xyz_abs()
|
||||
{
|
||||
return this->publish_gcode("G90 \n");
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_xyz_abs(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_auto_leveling()
|
||||
{
|
||||
return this->publish_gcode("G29 \n");
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_auto_leveling(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_go_home()
|
||||
{
|
||||
if (m_support_mqtt_homing)
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "back_to_center";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
return this->publish_json(j);
|
||||
}
|
||||
|
||||
// gcode command
|
||||
return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n");
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_go_home(get_dev_id(), this->is_in_printing(), m_support_mqtt_homing, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_task_partskip(std::vector<int> part_ids)
|
||||
@@ -1621,23 +1631,14 @@ int MachineObject::command_stop_buzzer()
|
||||
|
||||
int MachineObject::command_set_bed(int temp)
|
||||
{
|
||||
if (m_support_mqtt_bet_ctrl)
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "set_bed_temp";
|
||||
j["print"]["temp"] = temp;
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
return this->publish_json(j);
|
||||
}
|
||||
|
||||
std::string gcode_str = (boost::format("M140 S%1%\n") % temp).str();
|
||||
return this->publish_gcode(gcode_str);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_set_bed(get_dev_id(), temp, m_support_mqtt_bet_ctrl, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_set_nozzle(int temp)
|
||||
{
|
||||
std::string gcode_str = (boost::format("M104 S%1%\n") % temp).str();
|
||||
return this->publish_gcode(gcode_str);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_set_nozzle(get_dev_id(), temp, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_set_nozzle_new(int nozzle_id, int temp)
|
||||
@@ -1742,9 +1743,8 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
|
||||
|
||||
int MachineObject::command_ams_calibrate(int ams_id)
|
||||
{
|
||||
std::string gcode_cmd = (boost::format("M620 C%1% \n") % ams_id).str();
|
||||
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
|
||||
return this->publish_gcode(gcode_cmd);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
|
||||
@@ -1780,29 +1780,23 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
|
||||
return this->publish_json(j);
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_refresh_rfid(std::string tray_id)
|
||||
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
|
||||
{
|
||||
std::string gcode_cmd = (boost::format("M620 R%1% \n") % tray_id).str();
|
||||
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
|
||||
return this->publish_gcode(gcode_cmd);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id)
|
||||
int MachineObject::command_start_camera()
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "ams_get_rfid";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["ams_id"] = ams_id;
|
||||
j["print"]["slot_id"] = slot_id;
|
||||
return this->publish_json(j);
|
||||
if (!m_agent) return -1;
|
||||
return m_agent->command_start_camera(get_dev_id());
|
||||
}
|
||||
|
||||
|
||||
int MachineObject::command_ams_select_tray(std::string tray_id)
|
||||
{
|
||||
std::string gcode_cmd = (boost::format("M620 P%1% \n") % tray_id).str();
|
||||
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
|
||||
return this->publish_gcode(gcode_cmd);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_control(std::string action)
|
||||
@@ -1961,47 +1955,10 @@ int MachineObject::command_ams_air_print_detect(bool air_print_detect)
|
||||
|
||||
int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed)
|
||||
{
|
||||
if (m_support_mqtt_axis_control)
|
||||
{
|
||||
int dir = input_val > 0 ? 1 : -1;
|
||||
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
|
||||
// reversed — same negation the g-code fallback below applies.
|
||||
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
|
||||
dir = -dir;
|
||||
}
|
||||
|
||||
json j;
|
||||
j["print"]["command"] = "xyz_ctrl";
|
||||
j["print"]["axis"] = axis;
|
||||
j["print"]["dir"] = dir;
|
||||
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
return this->publish_json(j);
|
||||
}
|
||||
|
||||
double value = input_val;
|
||||
if (!is_core_xy()) {
|
||||
if ( axis.compare("Y") == 0
|
||||
|| axis.compare("Z") == 0) {
|
||||
value = -1.0 * input_val;
|
||||
}
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
if (axis.compare("X") == 0
|
||||
|| axis.compare("Y") == 0
|
||||
|| axis.compare("Z") == 0) {
|
||||
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
|
||||
}
|
||||
else if (axis.compare("E") == 0) {
|
||||
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return this->publish_gcode(cmd);
|
||||
if (!m_agent) return -1;
|
||||
return command_with_dialog(m_agent->command_axis_control(get_dev_id(), axis, unit, input_val, speed, is_core_xy(),
|
||||
m_support_mqtt_axis_control, MachineObject::m_sequence_id++,
|
||||
is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_extruder_control(int nozzle_id, double val)
|
||||
@@ -2626,7 +2583,12 @@ void MachineObject::reset()
|
||||
vt_slot.erase(vt_slot.begin() + 1);
|
||||
}
|
||||
}
|
||||
subtask_ = nullptr;
|
||||
// why: reset reuses MachineObject, so release its lazy subtask
|
||||
// before dropping the pointer to prevent reconnect leaks.
|
||||
if (subtask_) {
|
||||
delete subtask_;
|
||||
subtask_ = nullptr;
|
||||
}
|
||||
has_extra_flow_type = false;
|
||||
m_partskip_ids.clear();
|
||||
}
|
||||
@@ -2644,15 +2606,63 @@ void MachineObject::set_print_state(std::string status)
|
||||
}
|
||||
}
|
||||
|
||||
int MachineObject::connect(bool use_openssl)
|
||||
// why: printer agents can report progress without BBL cloud task identity.
|
||||
void MachineObject::update_print_progress(const json& value)
|
||||
{
|
||||
if (value.is_string()) {
|
||||
const std::string progress = value.get<std::string>();
|
||||
int parsed_progress;
|
||||
const auto result = std::from_chars(progress.data(), progress.data() + progress.size(), parsed_progress);
|
||||
if (result.ec != std::errc{} || result.ptr != progress.data() + progress.size())
|
||||
return;
|
||||
mc_print_percent = parsed_progress;
|
||||
} else if (value.is_number_integer())
|
||||
mc_print_percent = value.get<int>();
|
||||
else
|
||||
return;
|
||||
|
||||
if (BBLSubTask* curr_task = get_subtask())
|
||||
curr_task->task_progress = mc_print_percent;
|
||||
}
|
||||
|
||||
int MachineObject::connect()
|
||||
{
|
||||
if (get_dev_ip().empty()) return -1;
|
||||
std::string username = "bblp";
|
||||
std::string username = m_agent ? m_agent->default_lan_username() : std::string();
|
||||
std::string password = get_access_code();
|
||||
|
||||
std::string port;
|
||||
std::string input = get_dev_ip();
|
||||
|
||||
const bool use_ssl = input.rfind("https", 0) == 0;
|
||||
|
||||
// This strips out the http/https prefix
|
||||
std::string host = Http::get_host_from_url(input, &port);
|
||||
std::string ca_file;
|
||||
|
||||
if (GUI::wxGetApp().preset_bundle) {
|
||||
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (port.empty())
|
||||
port = config.opt_string("printhost_port");
|
||||
ca_file = config.opt_string("printhost_cafile");
|
||||
}
|
||||
|
||||
if (host.empty())
|
||||
host = get_dev_ip();
|
||||
|
||||
|
||||
if (m_agent) {
|
||||
try {
|
||||
return m_agent->connect_printer(get_dev_id(), get_dev_ip(), username, password, use_openssl);
|
||||
PrinterConnectionParams params{
|
||||
get_dev_id(),
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
use_ssl,
|
||||
ca_file
|
||||
};
|
||||
return m_agent->connect_printer(params);
|
||||
} catch (...) {
|
||||
;
|
||||
}
|
||||
@@ -2768,6 +2778,14 @@ int MachineObject::publish_json(const json& json_item, int qos, int flag)
|
||||
BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn;
|
||||
}
|
||||
|
||||
// why: the agent is the only thing that knows what it can translate, so it reports
|
||||
// not-supported in its return value and this - the single funnel every command_* builder
|
||||
// passes through - is the one place that turns it into something the user sees. No list of
|
||||
// unsupported commands is needed anywhere: an agent that has no case for a command says so.
|
||||
if (rtn == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || rtn == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) {
|
||||
show_unsupported_dlg(rtn);
|
||||
}
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
@@ -3086,6 +3104,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
try {
|
||||
if (j.contains("info"))
|
||||
parse_new_info2(j["info"]);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "parse_json: failed to parse OrcaSonar capability info";
|
||||
}
|
||||
|
||||
try {
|
||||
if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) {
|
||||
ptr->ParseFirmwareSwitch(j);
|
||||
@@ -3338,10 +3363,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
print_type = jj["print_type"].get<std::string>();
|
||||
}
|
||||
if (jj.contains("mc_percent")) {
|
||||
if (jj["mc_percent"].is_string())
|
||||
mc_print_percent = stoi(j["print"]["mc_percent"].get<std::string>());
|
||||
else if (jj["mc_percent"].is_number_integer())
|
||||
mc_print_percent = j["print"]["mc_percent"].get<int>();
|
||||
update_print_progress(jj["mc_percent"]);
|
||||
}
|
||||
if (jj.contains("mc_print_sub_stage")) {
|
||||
if (jj["mc_print_sub_stage"].is_number_integer())
|
||||
@@ -3511,6 +3533,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
this->task_id_ = jj["task_id"].get<std::string>();
|
||||
}
|
||||
|
||||
if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string())
|
||||
m_agent_thumbnail_url = jj["thumbnail_url"].get<std::string>();
|
||||
|
||||
if (jj.contains("job_attr")) {
|
||||
int jobAttr = jj["job_attr"].get<int>();
|
||||
jobState_ = get_flag_bits(jobAttr, 4, 4);
|
||||
@@ -3556,7 +3581,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index);
|
||||
BBLSubTask* curr_task = get_subtask();
|
||||
if (curr_task) {
|
||||
curr_task->task_progress = mc_print_percent;
|
||||
curr_task->printing_status = print_status;
|
||||
curr_task->task_id = jj["subtask_id"].get<std::string>();
|
||||
}
|
||||
@@ -3859,6 +3883,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
has_ipcam = true;
|
||||
} else {
|
||||
has_ipcam = false;
|
||||
webcam_stream_url.clear();
|
||||
}
|
||||
}
|
||||
if (ipcam.contains("resolution")) {
|
||||
@@ -3893,6 +3918,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable"
|
||||
? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp;
|
||||
}
|
||||
if (ipcam.contains("stream_url") && ipcam["stream_url"].is_string()) {
|
||||
webcam_stream_url = ipcam["stream_url"].get<std::string>();
|
||||
}
|
||||
if (ipcam.contains("tutk_server")) {
|
||||
tutk_state = ipcam["tutk_server"].get<std::string>();
|
||||
}
|
||||
@@ -4651,7 +4679,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
if (diff.count() > 10.0f) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count();
|
||||
}
|
||||
DeviceManager::update_local_machine(*this);
|
||||
DeviceManager::update_local_machine(*this, m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -5460,6 +5488,86 @@ void MachineObject::parse_new_info(json print)
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::parse_new_info2(const json& info)
|
||||
{
|
||||
if (!info.is_object() || info.value("command", "") != "get_capabilities")
|
||||
return;
|
||||
|
||||
const auto capabilities_it = info.find("capabilities");
|
||||
if (capabilities_it == info.end() || !capabilities_it->is_object())
|
||||
return;
|
||||
const auto flags_it = capabilities_it->find("flags");
|
||||
if (flags_it == capabilities_it->end() || !flags_it->is_object())
|
||||
return;
|
||||
|
||||
const json& flags = *flags_it;
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
|
||||
|
||||
auto parse_bool = [&flags](const char* name, bool& target) {
|
||||
const auto it = flags.find(name);
|
||||
if (it != flags.end() && it->is_boolean())
|
||||
target = it->get<bool>();
|
||||
};
|
||||
|
||||
parse_bool("support_send_to_sd", is_support_send_to_sdcard);
|
||||
parse_bool("support_filament_backup", is_support_filament_backup);
|
||||
parse_bool("support_update_remain", is_support_update_remain);
|
||||
parse_bool("support_auto_recovery_step_loss", is_support_auto_recovery_step_loss);
|
||||
parse_bool("support_ams_humidity", is_support_ams_humidity);
|
||||
parse_bool("support_prompt_sound", is_support_prompt_sound);
|
||||
parse_bool("support_filament_tangle_detect", is_support_filament_tangle_detect);
|
||||
parse_bool("support_1080dpi", is_support_1080dpi);
|
||||
parse_bool("support_cloud_print_only", is_support_cloud_print_only);
|
||||
parse_bool("support_command_ams_switch", is_support_command_ams_switch);
|
||||
parse_bool("support_mqtt_alive", is_support_mqtt_alive);
|
||||
parse_bool("support_motor_noise_cali", is_support_motor_noise_cali);
|
||||
parse_bool("support_timelapse", is_support_timelapse);
|
||||
parse_bool("support_user_preset", is_support_user_preset);
|
||||
parse_bool("support_refresh_nozzle", is_support_refresh_nozzle);
|
||||
parse_bool("support_flow_calibration", is_support_flow_calibration);
|
||||
parse_bool("support_build_plate_marker_detect", is_support_build_plate_marker_detect);
|
||||
parse_bool("support_nozzle_blob_detect", is_support_nozzle_blob_detection);
|
||||
|
||||
if (!m_manager->IsMultiMachineEnabled() && !is_support_agora)
|
||||
parse_bool("support_tunnel_mqtt", is_support_tunnel_mqtt);
|
||||
|
||||
const auto bed_leveling_it = flags.find("support_bed_leveling");
|
||||
if (bed_leveling_it != flags.end() && bed_leveling_it->is_number_integer())
|
||||
is_support_bed_leveling = bed_leveling_it->get<int>();
|
||||
|
||||
auto copy_bool = [&flags](json& target, const char* name) {
|
||||
const auto it = flags.find(name);
|
||||
if (it != flags.end() && it->is_boolean())
|
||||
target[name] = *it;
|
||||
};
|
||||
|
||||
// The capability manifest uses an object for this range, while the legacy
|
||||
// DeviceCore parser consumes a boolean plus a two-element range array.
|
||||
json device_config;
|
||||
copy_bool(device_config, "support_chamber");
|
||||
copy_bool(device_config, "support_first_layer_inspect");
|
||||
copy_bool(device_config, "support_ai_monitoring");
|
||||
copy_bool(device_config, "support_lidar_calibration");
|
||||
const auto chamber_edit_it = flags.find("support_chamber_temp_edit");
|
||||
if (chamber_edit_it != flags.end() && chamber_edit_it->is_boolean()) {
|
||||
device_config["support_chamber_temp_edit"] = *chamber_edit_it;
|
||||
} else if (chamber_edit_it != flags.end() && chamber_edit_it->is_object()) {
|
||||
const auto min_it = chamber_edit_it->find("min");
|
||||
const auto max_it = chamber_edit_it->find("max");
|
||||
if (min_it != chamber_edit_it->end() && max_it != chamber_edit_it->end() && min_it->is_number() && max_it->is_number()) {
|
||||
device_config["support_chamber_temp_edit"] = true;
|
||||
device_config["support_chamber_temp_edit_range"] = {*min_it, *max_it};
|
||||
}
|
||||
}
|
||||
|
||||
json fan_config;
|
||||
copy_bool(fan_config, "support_aux_fan");
|
||||
copy_bool(fan_config, "support_chamber_fan");
|
||||
|
||||
m_config->ParseConfig(device_config);
|
||||
m_fan->ParseV2_0(fan_config);
|
||||
}
|
||||
|
||||
static bool is_hex_digit(char c) {
|
||||
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
|
||||
}
|
||||
@@ -5965,6 +6073,15 @@ bool MachineObject::HasAms() const
|
||||
return m_fila_system->HasAms();
|
||||
}
|
||||
|
||||
int MachineObject::command_with_dialog(int cmd_result)
|
||||
{
|
||||
if (!m_agent)
|
||||
return -1;
|
||||
if (cmd_result == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || cmd_result == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE)
|
||||
show_unsupported_dlg(cmd_result);
|
||||
return cmd_result;
|
||||
}
|
||||
|
||||
void change_the_opacity(wxColour& colour)
|
||||
{
|
||||
if (colour.Alpha() == 255) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "boost/bimap/bimap.hpp"
|
||||
#include "libslic3r/calib.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/Utils/PrinterNetworkTypes.hpp"
|
||||
|
||||
#include "DeviceCore/DevDefs.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
@@ -100,7 +101,6 @@ struct DevPrintTaskRatingInfo;
|
||||
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
|
||||
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
|
||||
|
||||
|
||||
class MachineObject
|
||||
{
|
||||
private:
|
||||
@@ -307,7 +307,7 @@ public:
|
||||
bool ams_support_virtual_tray { true };
|
||||
time_t ams_user_setting_start = 0;
|
||||
time_t ams_switch_filament_start = 0;
|
||||
AmsStatusMain ams_status_main;
|
||||
AmsStatusMain ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_IDLE;
|
||||
int ams_status_sub;
|
||||
int ams_version = 0;
|
||||
|
||||
@@ -547,30 +547,12 @@ public:
|
||||
bool xcam_first_layer_inspector { false };
|
||||
time_t xcam_first_layer_hold_start = 0;
|
||||
std::string local_rtsp_url;
|
||||
std::string webcam_stream_url;
|
||||
std::string tutk_state;
|
||||
enum LiveviewLocal {
|
||||
LVL_None,
|
||||
LVL_Disable,
|
||||
LVL_Local,
|
||||
LVL_Rtsps,
|
||||
LVL_Rtsp
|
||||
} liveview_local{ LVL_None };
|
||||
enum LiveviewRemote {
|
||||
LVR_None,
|
||||
LVR_Tutk,
|
||||
LVR_Agora,
|
||||
LVR_TutkAgora
|
||||
} liveview_remote{ LVR_None };
|
||||
enum FileLocal {
|
||||
FL_None,
|
||||
FL_Local
|
||||
} file_local{ FL_None };
|
||||
enum FileRemote {
|
||||
FR_None,
|
||||
FR_Tutk,
|
||||
FR_Agora,
|
||||
FR_TutkAgora
|
||||
} file_remote{ FR_None };
|
||||
LiveviewLocal liveview_local{ LiveviewLocal::LVL_None };
|
||||
LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None};
|
||||
FileLocal file_local{ FileLocal::FL_None };
|
||||
FileRemote file_remote{ FileRemote::FR_None };
|
||||
|
||||
enum PlateMakerDectect : int
|
||||
{
|
||||
@@ -696,7 +678,7 @@ public:
|
||||
|
||||
|
||||
/* machine mqtt apis */
|
||||
int connect(bool use_openssl = true);
|
||||
int connect();
|
||||
int disconnect();
|
||||
|
||||
json_diff print_json;
|
||||
@@ -708,6 +690,8 @@ public:
|
||||
std::string subtask_id_;
|
||||
std::string job_id_;
|
||||
std::string last_subtask_id_;
|
||||
// note: printer-agent-supplied thumbnail url, empty when the agent supplies none.
|
||||
std::string m_agent_thumbnail_url;
|
||||
BBLSliceInfo* slice_info {nullptr};
|
||||
boost::thread* get_slice_info_thread { nullptr };
|
||||
boost::thread* get_model_task_thread { nullptr };
|
||||
@@ -765,6 +749,7 @@ public:
|
||||
int command_set_printer_nozzle(std::string nozzle_type, float diameter);
|
||||
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
|
||||
int command_get_access_code();
|
||||
int command_start_camera();
|
||||
int command_ack_proceed(json& proceed);
|
||||
int command_purification_disable();
|
||||
int command_dont_remind_next_time(json& mqtt_guard_json);
|
||||
@@ -811,8 +796,7 @@ public:
|
||||
int command_ams_calibrate(int ams_id);
|
||||
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
|
||||
int command_ams_select_tray(std::string tray_id);
|
||||
int command_ams_refresh_rfid(std::string tray_id);
|
||||
int command_ams_refresh_rfid2(int ams_id, int slot_id);
|
||||
int command_ams_refresh_rfid(int ams_id, int slot_id);
|
||||
int command_ams_control(std::string action);
|
||||
int command_ams_drying_stop();
|
||||
int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = "");
|
||||
@@ -894,6 +878,7 @@ public:
|
||||
static bool is_in_printing_status(std::string status);
|
||||
|
||||
void set_print_state(std::string status);
|
||||
void update_print_progress(const json& value);
|
||||
|
||||
bool is_connected();
|
||||
bool is_connecting();
|
||||
@@ -962,6 +947,7 @@ public:
|
||||
/*for parse new info*/
|
||||
bool check_enable_np(const json& print) const;
|
||||
void parse_new_info(json print);
|
||||
void parse_new_info2(const json& info);
|
||||
int get_flag_bits(std::string str, int start, int count = 1) const;
|
||||
uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const;
|
||||
int get_flag_bits(int num, int start, int count = 1, int base = 10) const;
|
||||
@@ -990,7 +976,7 @@ public:
|
||||
void command_set_save_remote_print_file_to_storage(bool save);
|
||||
|
||||
private:
|
||||
|
||||
int command_with_dialog(int cmd_result);
|
||||
/* xcam door open check*/
|
||||
bool is_support_door_open_check = false;
|
||||
DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE;
|
||||
|
||||
@@ -6577,8 +6577,11 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
|
||||
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
|
||||
|
||||
// Puts the canvas viewport back in place of the thumbnail one set above.
|
||||
wxGetApp().plater()->get_camera().apply_viewport();
|
||||
// Puts the canvas viewport back in place of the thumbnail one set above. The CLI renders
|
||||
// thumbnails with no application and no plater, so there is no canvas viewport to restore.
|
||||
if (wxTheApp != nullptr)
|
||||
if (Plater *plater = wxGetApp().plater(); plater != nullptr)
|
||||
plater->get_camera().apply_viewport();
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
||||
|
||||
+34
-47
@@ -4,6 +4,7 @@
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Shortcuts.hpp"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "BindDialog.hpp"
|
||||
#include "DeviceManager.hpp"
|
||||
#include "HMS.hpp"
|
||||
@@ -24,7 +25,6 @@
|
||||
#include <boost/locale/encoding_utf.hpp>
|
||||
#include <boost/log/detail/native_typeof.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <mutex>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <wx/event.h>
|
||||
|
||||
@@ -41,9 +41,11 @@
|
||||
#include <iterator>
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
@@ -88,7 +90,6 @@
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/plugin/host/PluginHostUi.hpp"
|
||||
#include "slic3r/plugin/PythonInterpreter.hpp"
|
||||
@@ -108,19 +109,18 @@
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/Process.hpp"
|
||||
#include "../Utils/wxInspectorPlugins/Registration.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "../Utils/Http.hpp"
|
||||
#include "../Utils/InstanceID.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "Preferences.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "SysInfoDialog.hpp"
|
||||
#include "UpdateDialogs.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
#ifdef __APPLE__
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "DeepLinkHandlerMac.h"
|
||||
#endif
|
||||
#include "NotificationManager.hpp"
|
||||
@@ -129,8 +129,6 @@
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "NetworkPluginDialog.hpp"
|
||||
#include "DesktopIntegrationDialog.hpp"
|
||||
#include "SendSystemInfoDialog.hpp"
|
||||
#include "ParamsDialog.hpp"
|
||||
#include "KBShortcutsDialog.hpp"
|
||||
#include "DownloadProgressDialog.hpp"
|
||||
#include "TroubleshootDialog.hpp"
|
||||
@@ -141,7 +139,6 @@
|
||||
#include "Widgets/ProgressDialog.hpp"
|
||||
|
||||
//BBS: DailyTip and UserGuide Dialog
|
||||
#include "WebDownPluginDlg.hpp"
|
||||
#include "WebGuideDialog.hpp"
|
||||
#include "ReleaseNote.hpp"
|
||||
#include "PrivacyUpdateDialog.hpp"
|
||||
@@ -2419,34 +2416,23 @@ GUI_App::~GUI_App()
|
||||
|
||||
bool GUI_App::is_blocking_printing(MachineObject *obj_)
|
||||
{
|
||||
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
std::string target_model;
|
||||
if (obj_ == nullptr) {
|
||||
obj_ = dev->get_selected_machine();
|
||||
if (obj_) {
|
||||
target_model = obj_->printer_type;
|
||||
}
|
||||
} else {
|
||||
target_model = obj_->printer_type;
|
||||
}
|
||||
|
||||
if (!obj_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
std::string source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
const std::string source_model = preset_bundle
|
||||
? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)
|
||||
: std::string();
|
||||
return is_blocking_printing(obj_, source_model);
|
||||
}
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool GUI_App::is_blocking_printing(MachineObject *obj_, const std::string& source_model)
|
||||
{
|
||||
DeviceManager *dev = getDeviceManager();
|
||||
if (!dev) return true;
|
||||
if (obj_ == nullptr)
|
||||
obj_ = dev->get_selected_machine();
|
||||
if (!obj_)
|
||||
return false;
|
||||
|
||||
return !DevPrinterConfigUtil::is_printer_model_compatible(source_model, *obj_);
|
||||
}
|
||||
|
||||
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
|
||||
@@ -4007,20 +3993,16 @@ void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
|
||||
m_agent->set_user_selected_machine("");
|
||||
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
|
||||
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
|
||||
// why: drop stale LAN discoveries; keep My Devices, but only those belonging to the
|
||||
// agent we're about to swap to, so a device stamped by the outgoing agent doesn't
|
||||
// linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh.
|
||||
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
|
||||
// target to filter against then, so fall back to the original "keep all My Devices"
|
||||
// behavior rather than guessing.
|
||||
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
|
||||
// why: retain agent-owned LAN discoveries so agents without automatic discovery (for
|
||||
// example the Moonraker-based Qidi/Snapmaker agents) can reuse them after a switch.
|
||||
dev->clear_other_devices();
|
||||
}
|
||||
|
||||
m_agent->set_printer_agent(agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
}
|
||||
|
||||
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
|
||||
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) const
|
||||
{
|
||||
if (!stored_id.empty())
|
||||
return stored_id;
|
||||
@@ -4056,6 +4038,7 @@ void GUI_App::switch_printer_agent()
|
||||
|
||||
std::string log_dir = data_dir();
|
||||
std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << agent_info.id;
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
|
||||
|
||||
// Create new printer agent via registry
|
||||
@@ -4069,8 +4052,10 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
// The factory caches agents per ID, so an identical pointer means the agent type is unchanged.
|
||||
if (m_agent->get_printer_agent() == new_printer_agent) {
|
||||
// Compare the registered IDs, not only the implementation pointer. Different registry IDs
|
||||
// may intentionally be backed by the same implementation object (especially for plugins).
|
||||
const auto current_printer_agent = m_agent->get_printer_agent();
|
||||
if (current_printer_agent && current_printer_agent->get_agent_info().id == effective_agent_id) {
|
||||
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
|
||||
// printer presets), so the selected machine and the agent's cached device_info still
|
||||
// point at the previously active printer preset. Re-select the machine when the new
|
||||
@@ -5053,14 +5038,16 @@ bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& GUI_App::get_printer_cloud_provider() const
|
||||
std::string GUI_App::get_printer_cloud_provider() const
|
||||
{
|
||||
// Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them.
|
||||
//
|
||||
return BBL_CLOUD_PROVIDER;
|
||||
const std::string agent_id = resolve_printer_agent_id(
|
||||
preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent")
|
||||
: std::string());
|
||||
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
#include <wx/snglinst.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <stack>
|
||||
|
||||
//#define BBL_HAS_FIRST_PAGE 1
|
||||
#define STUDIO_INACTIVE_TIMEOUT 15*60*1000
|
||||
#define LOG_FILES_MAX_NUM 30
|
||||
@@ -373,6 +370,7 @@ public:
|
||||
EAppMode get_app_mode() const { return m_app_mode; }
|
||||
Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; }
|
||||
bool is_blocking_printing(MachineObject *obj_ = nullptr);
|
||||
bool is_blocking_printing(MachineObject *obj_, const std::string& source_model);
|
||||
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
|
||||
HMSQuery* get_hms_query() { return hms_query; }
|
||||
NetworkAgent* getAgent() { return m_agent; }
|
||||
@@ -380,7 +378,7 @@ public:
|
||||
// Reconcile the live printer agent with the stored preset selection.
|
||||
void switch_printer_agent();
|
||||
|
||||
std::string resolve_printer_agent_id(const std::string& stored_id);
|
||||
std::string resolve_printer_agent_id(const std::string& stored_id) const;
|
||||
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
|
||||
// then, all resolve and canonical would just be ORCA<->""
|
||||
std::string canonical_printer_agent_id(const std::string& picked_id);
|
||||
@@ -511,7 +509,7 @@ public:
|
||||
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
const std::string& get_printer_cloud_provider() const;
|
||||
std::string get_printer_cloud_provider() const;
|
||||
|
||||
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/mediactrl.h>
|
||||
#include <wx/uri.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class IMediaController
|
||||
{
|
||||
public:
|
||||
virtual ~IMediaController() = default;
|
||||
|
||||
virtual void Load(wxURI url) = 0;
|
||||
|
||||
virtual void Play() = 0;
|
||||
|
||||
virtual void Stop() = 0;
|
||||
|
||||
virtual wxMediaState GetState() { return wxMediaState{}; }
|
||||
|
||||
virtual int GetLastError() const { return {}; };
|
||||
|
||||
virtual wxSize GetVideoSize() const { return {}; };
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "slic3r/Utils/FileTransferUtils.hpp"
|
||||
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -216,7 +217,7 @@ void PrintJob::process(Ctl &ctl)
|
||||
params.dev_ip = m_dev_ip;
|
||||
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl;
|
||||
params.username = "bblp";
|
||||
params.username = m_agent->default_lan_username();
|
||||
params.password = m_access_code;
|
||||
|
||||
// check access code and ip address
|
||||
|
||||
@@ -127,7 +127,7 @@ void SendJob::process(Ctl &ctl)
|
||||
if (m_is_check_mode) {
|
||||
PrintParams verify_params;
|
||||
verify_params.dev_ip = m_dev_ip;
|
||||
verify_params.username = "bblp";
|
||||
verify_params.username = agent->default_lan_username();
|
||||
verify_params.password = m_access_code;
|
||||
verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
|
||||
verify_params.use_ssl_for_mqtt = m_local_use_ssl;
|
||||
@@ -219,7 +219,7 @@ void SendJob::process(Ctl &ctl)
|
||||
|
||||
// local print access
|
||||
params.dev_ip = m_dev_ip;
|
||||
params.username = "bblp";
|
||||
params.username = agent->default_lan_username();
|
||||
params.password = m_access_code;
|
||||
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl;
|
||||
|
||||
@@ -4401,7 +4401,7 @@ void MainFrame::load_printer_url()
|
||||
if (auto *device_manager = wxGetApp().getDeviceManager()) {
|
||||
auto *machine = device_manager->get_selected_machine();
|
||||
if (!machine) {
|
||||
auto machines = device_manager->get_my_machine_list();
|
||||
auto machines = device_manager->get_my_machine_list(device_manager->get_current_printer_agent_id());
|
||||
if (machines.size() == 1)
|
||||
machine = machines.begin()->second;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "MediaPlayCtrl.h"
|
||||
#include "WebMediaController.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
@@ -7,6 +9,7 @@
|
||||
#include "DeviceManager.hpp"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -15,11 +18,15 @@
|
||||
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/utf8_codecvt.hpp>
|
||||
#include <slic3r/GUI/DeviceManager.hpp>
|
||||
#include <wx/mediactrl.h>
|
||||
#undef pid_t
|
||||
#include <boost/process.hpp>
|
||||
#ifdef __WIN32__
|
||||
@@ -48,6 +55,7 @@ namespace GUI {
|
||||
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
|
||||
: wxPanel(parent, wxID_ANY, pos, size)
|
||||
, m_media_ctrl(media_ctrl)
|
||||
, m_active_media_controller(media_ctrl)
|
||||
{
|
||||
SetLabel("MediaPlayCtrl");
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
@@ -94,7 +102,10 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
|
||||
});
|
||||
|
||||
m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); });
|
||||
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); });
|
||||
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) {
|
||||
if (m_active_media_controller)
|
||||
m_active_media_controller->Play();
|
||||
});
|
||||
// Orca: live-view FAQ link binding removed (vendor URL)
|
||||
|
||||
Bind(wxEVT_RIGHT_UP, [this](auto & e) {
|
||||
@@ -139,9 +150,12 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
|
||||
|
||||
MediaPlayCtrl::~MediaPlayCtrl()
|
||||
{
|
||||
if (m_webrtc_ctrl)
|
||||
m_webrtc_ctrl->Stop();
|
||||
m_media_ctrl->EndExternalStream();
|
||||
{
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_tasks.push_back("<exit>");
|
||||
m_tasks.push_back({"<exit>", nullptr});
|
||||
m_cond.notify_all();
|
||||
}
|
||||
while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) {
|
||||
@@ -151,59 +165,144 @@ MediaPlayCtrl::~MediaPlayCtrl()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this;
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::SetWebMediaController(IMediaController *ctrl)
|
||||
{
|
||||
m_web_ctrl = ctrl;
|
||||
set_active_media_controller(current_mode());
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::set_active_media_controller(CameraStreamMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case CameraStreamMode::http_snapshot:
|
||||
if (auto *web_ctrl = dynamic_cast<WebMediaController *>(m_web_ctrl))
|
||||
web_ctrl->set_mode(mode);
|
||||
m_active_media_controller = m_web_ctrl;
|
||||
break;
|
||||
case CameraStreamMode::webrtc:
|
||||
if (!m_webrtc_ctrl) {
|
||||
m_webrtc_ctrl = std::make_unique<WebRtcMediaController>(
|
||||
[this](const wxImage& image, wxSize size) { m_media_ctrl->SetExternalFrame(image, size); },
|
||||
[this, token = std::weak_ptr<int>(m_token)](WebRtcMediaController::Status status) {
|
||||
if (token.expired())
|
||||
return;
|
||||
CallAfter([this, status] { on_webrtc_status(status); });
|
||||
});
|
||||
}
|
||||
m_active_media_controller = m_webrtc_ctrl.get();
|
||||
break;
|
||||
default:
|
||||
m_active_media_controller = m_media_ctrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CameraStreamMode MediaPlayCtrl::current_mode() const
|
||||
{
|
||||
auto agent = wxGetApp().getAgent();
|
||||
return agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
|
||||
{
|
||||
std::string machine = obj ? obj->get_dev_id() : "";
|
||||
if (obj) {
|
||||
m_camera_exists = obj->has_ipcam;
|
||||
m_dev_ver = obj->get_ota_version();
|
||||
m_lan_mode = obj->is_lan_mode_printer();
|
||||
m_lan_proto = obj->liveview_local;
|
||||
m_remote_proto = obj->get_liveview_remote();
|
||||
m_lan_ip = obj->get_dev_ip();
|
||||
m_lan_passwd = obj->get_access_code();
|
||||
m_device_busy = obj->is_camera_busy_off();
|
||||
m_tutk_state = obj->tutk_state;
|
||||
|
||||
if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) {
|
||||
// Legacy plugin cannot support remote play for H2D, force using local mode
|
||||
m_remote_proto = MachineObject::LVR_None;
|
||||
const CameraStreamMode mode = current_mode();
|
||||
if (mode != m_last_mode) {
|
||||
if (m_last_state != MEDIASTATE_IDLE) {
|
||||
m_failed_code = 0; // a mode switch is not a stream failure - don't arm back-off
|
||||
Stop(" ");
|
||||
}
|
||||
} else {
|
||||
m_camera_exists = false;
|
||||
m_lan_mode = false;
|
||||
m_lan_proto = MachineObject::LVL_None;
|
||||
m_lan_ip.clear();
|
||||
m_lan_passwd.clear();
|
||||
m_dev_ver.clear();
|
||||
m_tutk_state.clear();
|
||||
m_remote_proto = 0;
|
||||
m_device_busy = false;
|
||||
m_last_mode = mode;
|
||||
}
|
||||
Enable(obj && obj->is_info_ready() && obj->m_push_count > 0);
|
||||
if (machine == m_machine) {
|
||||
if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
|
||||
Play();
|
||||
set_active_media_controller(mode);
|
||||
|
||||
const bool uses_local_camera_url = mode == CameraStreamMode::http || mode == CameraStreamMode::https ||
|
||||
mode == CameraStreamMode::http_snapshot || mode == CameraStreamMode::rtsp;
|
||||
const bool uses_webrtc = mode == CameraStreamMode::webrtc;
|
||||
const std::string machine = obj ? obj->get_dev_id() : "";
|
||||
bool changed = false;
|
||||
|
||||
if (uses_local_camera_url) {
|
||||
auto agent = wxGetApp().getAgent();
|
||||
std::string url = agent ? agent->get_local_camera_stream_url() : "";
|
||||
m_camera_exists = !url.empty();
|
||||
Enable(obj && m_camera_exists);
|
||||
changed = machine != m_machine || url != m_agent_camera_url;
|
||||
m_agent_camera_url = url;
|
||||
m_url = from_u8(url);
|
||||
} else if (uses_webrtc) {
|
||||
m_camera_exists = obj != nullptr;
|
||||
Enable(obj != nullptr);
|
||||
changed = machine != m_machine;
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetMachineObject webrtc: changed=" << changed
|
||||
<< " last_state=" << m_last_state << " web_user_stopped=" << m_web_user_stopped;
|
||||
m_url.clear();
|
||||
m_agent_camera_url.clear();
|
||||
} else {
|
||||
if (obj) {
|
||||
m_camera_exists = obj->has_ipcam;
|
||||
m_dev_ver = obj->get_ota_version();
|
||||
m_lan_mode = obj->is_lan_mode_printer();
|
||||
m_lan_proto = obj->liveview_local;
|
||||
m_remote_proto = obj->get_liveview_remote();
|
||||
m_lan_ip = obj->get_dev_ip();
|
||||
m_lan_passwd = obj->get_access_code();
|
||||
m_device_busy = obj->is_camera_busy_off();
|
||||
m_tutk_state = obj->tutk_state;
|
||||
|
||||
if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) {
|
||||
// Legacy plugin cannot support remote play for H2D, force using local mode
|
||||
m_remote_proto = LiveviewRemote::LVR_None;
|
||||
}
|
||||
} else {
|
||||
m_camera_exists = false;
|
||||
m_lan_mode = false;
|
||||
m_lan_proto = LiveviewLocal::LVL_None;
|
||||
m_lan_ip.clear();
|
||||
m_lan_passwd.clear();
|
||||
m_dev_ver.clear();
|
||||
m_tutk_state.clear();
|
||||
m_remote_proto = 0;
|
||||
m_device_busy = false;
|
||||
}
|
||||
Enable(obj && obj->is_info_ready() && obj->m_push_count > 0);
|
||||
if (machine == m_machine)
|
||||
return;
|
||||
|
||||
m_machine = machine;
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine;
|
||||
m_disable_lan = false;
|
||||
m_failed_retry = 0;
|
||||
m_last_failed_codes.clear();
|
||||
m_last_user_play = wxDateTime::Now();
|
||||
std::string stream_url;
|
||||
if (get_stream_url(&stream_url)) {
|
||||
m_streaming = boost::algorithm::contains(stream_url, "device=" + m_machine);
|
||||
} else {
|
||||
m_streaming = false;
|
||||
}
|
||||
if (m_last_state != MEDIASTATE_IDLE)
|
||||
Stop(" ");
|
||||
if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid state conflict
|
||||
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
|
||||
else
|
||||
SetStatus("", false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_machine = machine;
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine;
|
||||
m_disable_lan = false;
|
||||
m_failed_retry = 0;
|
||||
m_last_failed_codes.clear();
|
||||
m_last_user_play = wxDateTime::Now();
|
||||
std::string stream_url;
|
||||
if (get_stream_url(&stream_url)) {
|
||||
m_streaming = boost::algorithm::contains(stream_url, "device=" + m_machine);
|
||||
} else {
|
||||
m_streaming = false;
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
// A genuine target switch is not a stream failure and should clear the
|
||||
// manual-stop state before the new target is allowed to play.
|
||||
m_web_user_stopped = false;
|
||||
if (uses_local_camera_url) {
|
||||
m_failed_code = 0;
|
||||
m_failed_retry = 0;
|
||||
m_next_retry = wxDateTime();
|
||||
}
|
||||
if (m_last_state != MEDIASTATE_IDLE)
|
||||
Stop(" ");
|
||||
if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid state conflict
|
||||
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
|
||||
else
|
||||
SetStatus("", false);
|
||||
}
|
||||
|
||||
wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1)
|
||||
@@ -254,13 +353,71 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
|
||||
|
||||
void MediaPlayCtrl::Play()
|
||||
{
|
||||
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
|
||||
if ((m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) || !IsShownOnScreen() || m_last_state != MEDIASTATE_IDLE)
|
||||
return;
|
||||
if (!IsShownOnScreen())
|
||||
return;
|
||||
if (m_last_state != MEDIASTATE_IDLE) {
|
||||
|
||||
const CameraStreamMode mode = current_mode();
|
||||
set_active_media_controller(mode);
|
||||
m_last_mode = mode;
|
||||
auto agent = wxGetApp().getAgent();
|
||||
|
||||
auto printer_agent = agent ? agent->get_printer_agent() : nullptr;
|
||||
const bool is_bbl = (printer_agent ? printer_agent->get_agent_info().id : "") == BBL_PRINTER_AGENT_ID;
|
||||
|
||||
if (!is_bbl) {
|
||||
const bool is_webrtc = mode == CameraStreamMode::webrtc;
|
||||
const bool is_snapshot = mode == CameraStreamMode::http_snapshot;
|
||||
const bool is_http_stream = mode == CameraStreamMode::http || mode == CameraStreamMode::https || mode == CameraStreamMode::rtsp;
|
||||
if (!is_webrtc && !is_snapshot && !is_http_stream)
|
||||
return;
|
||||
|
||||
auto *webrtc_ctrl = is_webrtc ? dynamic_cast<WebRtcMediaController *>(m_active_media_controller) : nullptr;
|
||||
if (is_webrtc && (!webrtc_ctrl || !m_media_ctrl)) {
|
||||
Stop(_L("Please confirm if the printer is connected."));
|
||||
return;
|
||||
}
|
||||
if (webrtc_ctrl && webrtc_ctrl->is_active())
|
||||
return;
|
||||
|
||||
m_failed_code = 0;
|
||||
if (!m_active_media_controller || m_machine.empty() || !IsEnabled() || !m_camera_exists ||
|
||||
(!is_webrtc && m_url.IsEmpty())) {
|
||||
Stop(_L("Please confirm if the printer is connected."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_webrtc) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play webrtc: last_state=" << m_last_state << " failed_retry=" << m_failed_retry
|
||||
<< " shown=" << IsShownOnScreen();
|
||||
auto channel = agent ? agent->create_camera_signaling_channel(m_machine, wxGetApp().get_printer_cloud_provider()) : nullptr;
|
||||
if (!channel) {
|
||||
Stop(_L("Sign in to OrcaCloud to view the camera."));
|
||||
return;
|
||||
}
|
||||
webrtc_ctrl->set_signalling_channel(std::move(channel));
|
||||
m_media_ctrl->BeginExternalStream();
|
||||
m_last_state = MEDIASTATE_INITIALIZING;
|
||||
SetStatus(_L("Initializing..."), false);
|
||||
} else if (is_snapshot) {
|
||||
m_last_state = wxMEDIASTATE_PLAYING;
|
||||
SetStatus(_L("Playing..."), false);
|
||||
}
|
||||
|
||||
m_button_play->SetIcon("media_stop");
|
||||
if (m_active_media_controller == m_media_ctrl) {
|
||||
// wxMediaCtrl3 reports when it has a decoded frame; load() waits for
|
||||
// that event before queuing Play so the stream is not marked stopped.
|
||||
load();
|
||||
} else {
|
||||
m_active_media_controller->Load(wxURI(m_url));
|
||||
m_active_media_controller->Play();
|
||||
}
|
||||
if (webrtc_ctrl) {
|
||||
m_webrtc_epoch = webrtc_ctrl->epoch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_failed_code = 0;
|
||||
if (m_machine.empty()) {
|
||||
Stop(_L("Please confirm if the printer is connected."));
|
||||
@@ -281,16 +438,15 @@ void MediaPlayCtrl::Play()
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan;
|
||||
NetworkAgent *agent = wxGetApp().getAgent();
|
||||
std::string agent_version = agent ? agent->get_version() : "";
|
||||
if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
|
||||
if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
|
||||
m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time
|
||||
std::string url;
|
||||
if (m_lan_proto == MachineObject::LVL_Local)
|
||||
if (m_lan_proto == LiveviewLocal::LVL_Local)
|
||||
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
|
||||
else if (m_lan_proto == MachineObject::LVL_Rtsps)
|
||||
else if (m_lan_proto == LiveviewLocal::LVL_Rtsps)
|
||||
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
|
||||
else if (m_lan_proto == MachineObject::LVL_Rtsp)
|
||||
else if (m_lan_proto == LiveviewLocal::LVL_Rtsp)
|
||||
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
|
||||
url += "&device=" + m_machine;
|
||||
url += "&net_ver=" + agent_version;
|
||||
@@ -312,8 +468,8 @@ void MediaPlayCtrl::Play()
|
||||
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*)
|
||||
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x)
|
||||
|
||||
if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) {
|
||||
Stop(m_lan_proto == MachineObject::LVL_None
|
||||
if (m_lan_proto <= LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto)) {
|
||||
Stop(m_lan_proto == LiveviewLocal::LVL_None
|
||||
? _L("A problem occurred. Please update the printer firmware and try again.")
|
||||
: _L("LAN Only Liveview is off. Please turn on the liveview on printer screen."));
|
||||
return;
|
||||
@@ -381,15 +537,78 @@ void MediaPlayCtrl::Play()
|
||||
|
||||
void start_ping_test();
|
||||
|
||||
void MediaPlayCtrl::StopWebStream()
|
||||
{
|
||||
if (m_last_state == MEDIASTATE_IDLE)
|
||||
return;
|
||||
if (m_active_media_controller && m_active_media_controller == m_web_ctrl)
|
||||
m_active_media_controller->Stop();
|
||||
m_button_play->SetIcon("media_play");
|
||||
m_last_state = MEDIASTATE_IDLE;
|
||||
SetStatus(_L("Video Stopped."), false);
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
|
||||
{
|
||||
const bool webrtc_active = m_last_mode == CameraStreamMode::webrtc &&
|
||||
dynamic_cast<WebRtcMediaController *>(m_active_media_controller) != nullptr;
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Stop: last_state=" << m_last_state
|
||||
<< " webrtc_active=" << webrtc_active << " failed_code=" << m_failed_code
|
||||
<< " msg='" << msg.ToUTF8().data() << "'";
|
||||
if (webrtc_active) {
|
||||
m_active_media_controller->Stop();
|
||||
m_media_ctrl->EndExternalStream();
|
||||
}
|
||||
switch (m_last_mode) {
|
||||
case CameraStreamMode::http:
|
||||
case CameraStreamMode::https:
|
||||
case CameraStreamMode::http_snapshot: {
|
||||
const bool snapshot = m_last_mode == CameraStreamMode::http_snapshot;
|
||||
if (m_last_state != MEDIASTATE_IDLE) {
|
||||
if (snapshot) {
|
||||
if (m_active_media_controller)
|
||||
m_active_media_controller->Stop();
|
||||
} else {
|
||||
// http/https mode plays through the ffmpeg backend (m_media_ctrl), not
|
||||
// the webview - tear its read thread down too, otherwise it keeps
|
||||
// pulling and painting frames after the UI says "Video Stopped".
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_tasks.push_back({"<stop>", m_active_media_controller});
|
||||
m_cond.notify_all();
|
||||
}
|
||||
m_button_play->SetIcon("media_play");
|
||||
m_last_state = MEDIASTATE_IDLE;
|
||||
if (!msg.IsEmpty())
|
||||
SetStatus(msg);
|
||||
else
|
||||
SetStatus(_L("Video Stopped."), false);
|
||||
// Keep retries bounded for an explicit or retry-driven playback attempt.
|
||||
// m_failed_retry is cleared on success (onStateChanged) and on a deliberate
|
||||
// machine switch (SetMachineObject); manual playback via TogglePlay resets it.
|
||||
if (m_failed_code != 0) {
|
||||
const bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false";
|
||||
++m_failed_retry;
|
||||
m_next_retry = auto_retry
|
||||
? wxDateTime::Now() + wxTimeSpan::Seconds(std::min(5 * m_failed_retry, 30))
|
||||
: wxDateTime::Now() + wxTimeSpan::Days(1); // "off": wait for a manual retry
|
||||
}
|
||||
} else if (!msg.IsEmpty()) {
|
||||
SetStatus(msg, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int last_state = m_last_state;
|
||||
|
||||
if (m_last_state != MEDIASTATE_IDLE) {
|
||||
m_media_ctrl->InvalidateBestSize();
|
||||
m_button_play->SetIcon("media_play");
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_tasks.push_back("<stop>");
|
||||
if (!webrtc_active)
|
||||
m_tasks.push_back({"<stop>", m_active_media_controller});
|
||||
m_cond.notify_all();
|
||||
if (!msg.IsEmpty())
|
||||
SetStatus(msg);
|
||||
@@ -454,15 +673,38 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
|
||||
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry);
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::on_webrtc_status(WebRtcMediaController::Status status)
|
||||
{
|
||||
// Drop CallAfter-queued events from a superseded Play attempt.
|
||||
if (status.epoch != m_webrtc_epoch)
|
||||
return;
|
||||
if (status.kind == WebRtcMediaController::Status::Connecting) {
|
||||
m_last_state = MEDIASTATE_INITIALIZING;
|
||||
SetStatus(_L("Initializing..."), false);
|
||||
} else if (status.kind == WebRtcMediaController::Status::Playing) {
|
||||
m_last_state = wxMEDIASTATE_PLAYING;
|
||||
m_failed_code = 0;
|
||||
m_failed_retry = 0;
|
||||
SetStatus(_L("Playing..."), false);
|
||||
} else if (status.kind == WebRtcMediaController::Status::Failed) {
|
||||
m_failed_code = static_cast<int>(status.code) + 1;
|
||||
Stop();
|
||||
}
|
||||
// Status::Stopped needs no action: a genuine failure arrives as Failed, and
|
||||
// a stop we initiated is already handled by Stop() itself.
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::TogglePlay()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay";
|
||||
if (m_last_state != MEDIASTATE_IDLE) {
|
||||
m_next_retry = wxDateTime();
|
||||
m_web_user_stopped = true;
|
||||
Stop();
|
||||
} else {
|
||||
m_failed_retry = 0;
|
||||
m_user_triggered = true;
|
||||
m_web_user_stopped = false;
|
||||
if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) {
|
||||
m_last_failed_codes.clear();
|
||||
m_last_user_play = wxDateTime::Now();
|
||||
@@ -528,13 +770,13 @@ void MediaPlayCtrl::ToggleStream()
|
||||
wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1");
|
||||
if (res == wxID_CANCEL) return;
|
||||
}
|
||||
if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
|
||||
if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
|
||||
std::string url;
|
||||
if (m_lan_proto == MachineObject::LVL_Local)
|
||||
if (m_lan_proto == LiveviewLocal::LVL_Local)
|
||||
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
|
||||
else if (m_lan_proto == MachineObject::LVL_Rtsps)
|
||||
else if (m_lan_proto == LiveviewLocal::LVL_Rtsps)
|
||||
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
|
||||
else if (m_lan_proto == MachineObject::LVL_Rtsp)
|
||||
else if (m_lan_proto == LiveviewLocal::LVL_Rtsp)
|
||||
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
|
||||
url += "&device=" + into_u8(m_machine);
|
||||
url += "&dev_ver=" + m_dev_ver;
|
||||
@@ -594,7 +836,9 @@ void MediaPlayCtrl::jump_to_play()
|
||||
void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
|
||||
{
|
||||
auto last_state = m_last_state;
|
||||
auto state = m_media_ctrl->GetState();
|
||||
if (m_active_media_controller != m_media_ctrl)
|
||||
return;
|
||||
auto state = m_active_media_controller->GetState();
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state;
|
||||
if ((int) state < 0) return;
|
||||
{
|
||||
@@ -606,14 +850,14 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
|
||||
}
|
||||
if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; }
|
||||
if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) {
|
||||
m_failed_code = m_media_ctrl->GetLastError();
|
||||
m_failed_code = m_active_media_controller->GetLastError();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) {
|
||||
wxSize size = m_media_ctrl->GetVideoSize();
|
||||
wxSize size = m_active_media_controller->GetVideoSize();
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y;
|
||||
m_failed_code = m_media_ctrl->GetLastError();
|
||||
m_failed_code = m_active_media_controller->GetLastError();
|
||||
if (size.GetWidth() >= 320) {
|
||||
m_last_state = state;
|
||||
m_failed_code = 0;
|
||||
@@ -623,7 +867,7 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
|
||||
m_failed_retry = 0;
|
||||
m_disable_lan = false;
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_tasks.push_back("<play>");
|
||||
m_tasks.push_back({"<play>", m_active_media_controller});
|
||||
m_cond.notify_all();
|
||||
} else if (event.GetId()) {
|
||||
if (m_failed_code == 0)
|
||||
@@ -666,7 +910,8 @@ void MediaPlayCtrl::load()
|
||||
{
|
||||
m_last_state = MEDIASTATE_LOADING;
|
||||
SetStatus(_L("Loading..."));
|
||||
if (wxGetApp().app_config->get("internal_developer_mode") == "true") {
|
||||
const auto mode = current_mode();
|
||||
if (mode == CameraStreamMode::none && wxGetApp().app_config->get("internal_developer_mode") == "true") {
|
||||
std::string file_h264 = data_dir() + "/video.h264";
|
||||
std::string file_info = data_dir() + "/video.info";
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
|
||||
@@ -677,7 +922,7 @@ void MediaPlayCtrl::load()
|
||||
m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file);
|
||||
}
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_tasks.push_back(m_url);
|
||||
m_tasks.push_back({m_url, m_active_media_controller});
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
@@ -686,9 +931,8 @@ void MediaPlayCtrl::on_show_hide(wxShowEvent &evt)
|
||||
evt.Skip();
|
||||
if (m_isBeingDeleted) return;
|
||||
m_failed_retry = 0;
|
||||
if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid quick play/stop
|
||||
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
|
||||
IsShownOnScreen() ? Play() : Stop();
|
||||
if (!IsShownOnScreen())
|
||||
Stop();
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::media_proc()
|
||||
@@ -698,30 +942,34 @@ void MediaPlayCtrl::media_proc()
|
||||
while (m_tasks.empty()) {
|
||||
m_cond.wait(lock);
|
||||
}
|
||||
wxString url = m_tasks.front();
|
||||
if (m_tasks.size() >= 2 && !url.IsEmpty() && url[0] != '<' && m_tasks[1] == "<stop>") {
|
||||
BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << url;
|
||||
MediaTask task = m_tasks.front();
|
||||
if (m_tasks.size() >= 2 && !task.command.IsEmpty() && task.command[0] != '<' &&
|
||||
m_tasks[1].command == "<stop>" && task.controller == m_tasks[1].controller) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << task.command;
|
||||
m_tasks.pop_front();
|
||||
m_tasks.pop_front();
|
||||
continue;
|
||||
}
|
||||
lock.unlock();
|
||||
if (url == "<stop>") {
|
||||
if (task.command == "<stop>") {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop";
|
||||
m_media_ctrl->Stop();
|
||||
if (task.controller)
|
||||
task.controller->Stop();
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop";
|
||||
}
|
||||
else if (url == "<exit>") {
|
||||
else if (task.command == "<exit>") {
|
||||
break;
|
||||
}
|
||||
else if (url == "<play>") {
|
||||
else if (task.command == "<play>") {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
|
||||
m_media_ctrl->Play();
|
||||
if (task.controller)
|
||||
task.controller->Play();
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
|
||||
m_media_ctrl->Load(wxURI(url));
|
||||
if (task.controller)
|
||||
task.controller->Load(wxURI(task.command));
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load";
|
||||
}
|
||||
lock.lock();
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#define MediaPlayCtrl_h
|
||||
|
||||
#include "wxMediaCtrl3.h"
|
||||
#include "IMediaController.hpp"
|
||||
#include "WebRtcMediaController.hpp"
|
||||
#include "slic3r/Utils/IPrinterAgent.hpp"
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
@@ -36,6 +39,10 @@ public:
|
||||
|
||||
void SetMachineObject(MachineObject * obj);
|
||||
|
||||
void SetWebMediaController(IMediaController *ctrl);
|
||||
|
||||
void StopWebStream();
|
||||
|
||||
bool IsStreaming() const;
|
||||
|
||||
void ToggleStream();
|
||||
@@ -54,6 +61,7 @@ protected:
|
||||
void TogglePlay();
|
||||
|
||||
void SetStatus(wxString const &msg, bool hyperlink = true);
|
||||
void on_webrtc_status(WebRtcMediaController::Status status);
|
||||
|
||||
private:
|
||||
void load();
|
||||
@@ -66,6 +74,9 @@ private:
|
||||
|
||||
static bool get_stream_url(std::string *url = nullptr);
|
||||
|
||||
CameraStreamMode current_mode() const;
|
||||
void set_active_media_controller(CameraStreamMode mode);
|
||||
|
||||
private:
|
||||
static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3);
|
||||
static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4);
|
||||
@@ -76,6 +87,13 @@ private:
|
||||
std::shared_ptr<int> m_token = std::make_shared<int>(0);
|
||||
|
||||
wxMediaCtrl3 * m_media_ctrl;
|
||||
IMediaController * m_active_media_controller = nullptr;
|
||||
IMediaController * m_web_ctrl = nullptr;
|
||||
std::unique_ptr<WebRtcMediaController> m_webrtc_ctrl;
|
||||
CameraStreamMode m_last_mode = CameraStreamMode::none;
|
||||
std::uint64_t m_webrtc_epoch = 0;
|
||||
std::string m_agent_camera_url;
|
||||
bool m_web_user_stopped = false;
|
||||
wxMediaState m_last_state = MEDIASTATE_IDLE;
|
||||
std::string m_machine;
|
||||
int m_lan_proto = 0;
|
||||
@@ -91,7 +109,11 @@ private:
|
||||
bool m_disable_lan = false;
|
||||
wxString m_url;
|
||||
|
||||
std::deque<wxString> m_tasks;
|
||||
struct MediaTask {
|
||||
wxString command;
|
||||
IMediaController *controller = nullptr;
|
||||
};
|
||||
std::deque<MediaTask> m_tasks;
|
||||
boost::mutex m_mutex;
|
||||
boost::condition_variable m_cond;
|
||||
boost::thread m_thread;
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#include "DeviceCore/DevManager.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@@ -265,6 +267,7 @@ void MonitorPanel::msw_rescale()
|
||||
|
||||
void MonitorPanel::select_machine(std::string machine_sn)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::select_machine queueing machine_sn=" << machine_sn;
|
||||
wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED);
|
||||
event->SetString(machine_sn);
|
||||
wxQueueEvent(this, event);
|
||||
@@ -282,13 +285,20 @@ void MonitorPanel::on_timer(wxTimerEvent& event)
|
||||
void MonitorPanel::on_select_printer(wxCommandEvent& event)
|
||||
{
|
||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
const std::string requested_dev_id = event.GetString().ToStdString();
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer requested_dev_id="
|
||||
<< requested_dev_id << " device_manager=" << (dev ? "set" : "null");
|
||||
if (!dev) return;
|
||||
|
||||
if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) {
|
||||
m_hms_panel->clear_hms_tag();
|
||||
}
|
||||
|
||||
if (!dev->set_selected_machine(event.GetString().ToStdString()))
|
||||
const bool selected = dev->set_selected_machine(requested_dev_id);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer set_selected_machine result="
|
||||
<< selected << " selected_dev_id="
|
||||
<< (dev->get_selected_machine() ? dev->get_selected_machine()->get_dev_id() : "<null>");
|
||||
if (!selected)
|
||||
return;
|
||||
|
||||
set_default();
|
||||
|
||||
@@ -51,7 +51,7 @@ void DeviceItem::sync_state()
|
||||
state_printable = 6;
|
||||
}
|
||||
|
||||
if (is_blocking_printing(obj_)) {
|
||||
if (wxGetApp().is_blocking_printing(obj_)) {
|
||||
state_printable = 5;
|
||||
}
|
||||
|
||||
@@ -104,27 +104,6 @@ void DeviceItem::unselected()
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceItem::is_blocking_printing(MachineObject* obj_)
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
auto target_model = obj_->printer_type;
|
||||
std::string source_model = "";
|
||||
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeviceItem::update_item(const DeviceItem* item)
|
||||
{
|
||||
// Except for the selected status, everything else is updated
|
||||
|
||||
@@ -59,7 +59,6 @@ public:
|
||||
|
||||
void selected();
|
||||
void unselected();
|
||||
bool is_blocking_printing(MachineObject* obj_);
|
||||
void update_item(const DeviceItem* item);
|
||||
};
|
||||
|
||||
|
||||
@@ -159,4 +159,4 @@ private:
|
||||
void OnApplyDialog(wxCommandEvent &event);
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
+89
-24
@@ -2239,12 +2239,14 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
|
||||
std::string machine_print_name = obj->get_show_printer_type();
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle);
|
||||
Preset* machine_preset = get_printer_preset(obj);
|
||||
if (!machine_preset) {
|
||||
const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type);
|
||||
const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id);
|
||||
Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj);
|
||||
if (!optional_printer_model && !optional_target_model && !machine_preset) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty";
|
||||
return false;
|
||||
}
|
||||
if (machine_print_name != target_model_id) {
|
||||
if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) {
|
||||
MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n"
|
||||
"Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_NO) {
|
||||
@@ -2436,6 +2438,11 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
|
||||
return;
|
||||
}
|
||||
|
||||
if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) {
|
||||
clear_all_sync_status();
|
||||
return;
|
||||
}
|
||||
|
||||
bool printer_synced = false;
|
||||
// 1. update printer status
|
||||
const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
@@ -6069,11 +6076,30 @@ void Sidebar::load_ams_list(MachineObject* obj)
|
||||
filament_ams_list = build_filament_ams_list(obj);
|
||||
}
|
||||
|
||||
bool device_change = false;
|
||||
const std::string& device = obj ? obj->get_dev_id() : "";
|
||||
if (p->ams_list_device != device) {
|
||||
const bool same_device = p->ams_list_device == device;
|
||||
|
||||
// Keep sync metadata out of the device payload, but preserve it across a
|
||||
// subscription refresh when the physical filament in a slot is unchanged.
|
||||
// Otherwise the refreshed configs differ only by the missing
|
||||
// filament_changed key, causing combo boxes to rebuild and lose their
|
||||
// transient post-sync badges.
|
||||
auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list;
|
||||
for (auto &entry : filament_ams_list) {
|
||||
auto previous = previous_filament_ams_list.find(entry.first);
|
||||
const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr :
|
||||
dynamic_cast<const ConfigOptionBool *>(previous->second.option("filament_changed"));
|
||||
if (!same_device || previous_changed == nullptr ||
|
||||
previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) {
|
||||
continue;
|
||||
}
|
||||
entry.second.set_key_value("filament_changed",
|
||||
new ConfigOptionBool{previous_changed->value});
|
||||
}
|
||||
|
||||
bool device_change = !same_device;
|
||||
if (device_change) {
|
||||
p->ams_list_device = device;
|
||||
device_change = true;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size();
|
||||
if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change)
|
||||
@@ -6083,9 +6109,27 @@ void Sidebar::load_ams_list(MachineObject* obj)
|
||||
wxGetApp().preset_bundle->filament_ams_list = filament_ams_list;
|
||||
|
||||
for (auto c : p->combos_filament){
|
||||
c->set_sync_badge(false);
|
||||
c->update();
|
||||
if (device_change) {
|
||||
c->ShowBadge(false);//change printer,then clear badge
|
||||
}
|
||||
|
||||
if (!device_change) {
|
||||
size_t combo_index = 0;
|
||||
for (const auto &entry : filament_ams_list) {
|
||||
const auto &tray = entry.second;
|
||||
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
|
||||
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
|
||||
tray.opt_bool("filament_slot_placeholder", 0u);
|
||||
if (!has_filament && !is_placeholder) {
|
||||
continue;
|
||||
}
|
||||
if (combo_index >= p->combos_filament.size()) {
|
||||
break;
|
||||
}
|
||||
const auto *filament_changed = dynamic_cast<const ConfigOptionBool *>(tray.option("filament_changed"));
|
||||
p->combos_filament[combo_index]->set_sync_badge(
|
||||
has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value);
|
||||
++combo_index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6259,18 +6303,32 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") :
|
||||
_L("Filament type and color information have been synchronized, but slot information is not included.");
|
||||
c->SetToolTip(tip);
|
||||
c->ShowBadge(true);
|
||||
c->set_sync_badge(true);
|
||||
};
|
||||
{ // badge ams filament
|
||||
clear_combos_filament_badge();
|
||||
if (sync_result.direct_sync) {
|
||||
// Orca: PresetBundle::sync_ams_list rebuilds combos_filament
|
||||
// 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder
|
||||
// empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The
|
||||
// previous per-tray index walked the full filament_ams_list (including the skipped empties),
|
||||
// so an empty slot before a loaded one dropped the badge for the trailing filaments.
|
||||
for (auto &c : p->combos_filament) {
|
||||
badge_combox_filament(c);
|
||||
// A placeholder contributes a preserved project filament to the
|
||||
// overwrite result, but it is not AMS-sourced and must not get a
|
||||
// sync badge. Non-placeholder empty trays are omitted entirely.
|
||||
size_t combo_index = 0;
|
||||
for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) {
|
||||
const auto &tray = entry.second;
|
||||
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
|
||||
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
|
||||
tray.opt_bool("filament_slot_placeholder", 0u);
|
||||
if (!has_filament && !is_placeholder) {
|
||||
continue;
|
||||
}
|
||||
if (combo_index >= p->combos_filament.size()) {
|
||||
break;
|
||||
}
|
||||
if (is_placeholder) {
|
||||
p->combos_filament[combo_index]->set_sync_badge(false);
|
||||
} else {
|
||||
badge_combox_filament(p->combos_filament[combo_index]);
|
||||
}
|
||||
++combo_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6538,6 +6596,11 @@ template<typename T> void setup_dialog_position(T& info)
|
||||
|
||||
void Sidebar::pop_sync_nozzle_and_ams_dialog() {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog";
|
||||
auto agent = wxGetApp().getAgent();
|
||||
if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog";
|
||||
return;
|
||||
}
|
||||
wxTheApp->CallAfter([this]() {
|
||||
SyncNozzleAndAmsDialog::InputInfo temp_na_info;
|
||||
wxPoint big_btn_pt;
|
||||
@@ -6669,17 +6732,14 @@ void Sidebar::clear_combos_filament_badge()
|
||||
{
|
||||
auto &combos_filament = p->combos_filament;
|
||||
for (auto &c : combos_filament) { // clear flag
|
||||
c->ShowBadge(false);
|
||||
c->set_sync_badge(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::udpate_combos_filament_badge() {
|
||||
auto &combos_filament = p->combos_filament;
|
||||
for (auto &c : combos_filament) {
|
||||
auto selection = c->GetSelection();
|
||||
auto select_flag = c->GetFlag(selection);
|
||||
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
|
||||
c->ShowBadge(ok);
|
||||
c->update_badge_according_flag();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12702,7 +12762,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
sidebar->auto_calc_flushing_volumes(idx);
|
||||
}
|
||||
auto select_flag = combo->GetFlag(selection);
|
||||
combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
|
||||
combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
|
||||
q->on_filament_change(idx);
|
||||
}
|
||||
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
|
||||
@@ -21598,9 +21658,14 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning)
|
||||
}
|
||||
if (!check_printer_initialized(obj, true, popup_warning))
|
||||
return false;
|
||||
Preset * machine_preset = get_printer_preset(obj);
|
||||
if (!machine_preset)
|
||||
const std::string machine_model = obj->printer_type;
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string();
|
||||
if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) &&
|
||||
!DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) &&
|
||||
!get_printer_preset(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wxGetApp().is_blocking_printing()) {
|
||||
if (popup_warning) {
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Slic3r
|
||||
// IMPORTANT: ordinal order is the Plugins dialog Status sort priority.
|
||||
Activated,
|
||||
Error,
|
||||
RuntimeError,
|
||||
Inactive,
|
||||
Loading
|
||||
};
|
||||
@@ -21,11 +22,28 @@ namespace Slic3r
|
||||
{
|
||||
case PluginStatus::Activated: return "Activated";
|
||||
case PluginStatus::Error: return "Error";
|
||||
case PluginStatus::RuntimeError: return "RuntimeError";
|
||||
case PluginStatus::Inactive: return "Inactive";
|
||||
case PluginStatus::Loading: return "Loading";
|
||||
}
|
||||
|
||||
return "Inactive";
|
||||
}
|
||||
|
||||
// why: a plugin whose module is live but whose catalog carries an error is a
|
||||
// RUNTIME fault (e.g. a capability rejected at register time) - it stays
|
||||
// loaded/checked and is only flagged, distinct from a load-time Error where
|
||||
// the module never came up. Loading wins over both so an in-flight reload
|
||||
// never flashes an error.
|
||||
inline PluginStatus resolve_plugin_status(bool loading, bool has_error, bool is_loaded)
|
||||
{
|
||||
if (loading)
|
||||
return PluginStatus::Loading;
|
||||
if (has_error)
|
||||
return is_loaded ? PluginStatus::RuntimeError : PluginStatus::Error;
|
||||
if (is_loaded)
|
||||
return PluginStatus::Activated;
|
||||
return PluginStatus::Inactive;
|
||||
}
|
||||
}
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
@@ -193,6 +193,7 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
|
||||
payload_item["label"] = dialog_item.display_name;
|
||||
payload_item["source"] = to_string(dialog_item.source);
|
||||
payload_item["status"] = to_string(dialog_item.status);
|
||||
payload_item["is_loaded"] = dialog_item.is_loaded;
|
||||
payload_item["error"] = dialog_item.error_text;
|
||||
payload_item["update_status"] = to_string(dialog_item.update_status);
|
||||
payload_item["unauthorized"] = dialog_item.unauthorized;
|
||||
@@ -337,14 +338,7 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
|
||||
item.sharing_token = descriptor.sharing_token;
|
||||
item.thumbnail_url = descriptor.thumbnail_url;
|
||||
|
||||
if (item.loading)
|
||||
item.status = PluginStatus::Loading;
|
||||
else if (item.has_error)
|
||||
item.status = PluginStatus::Error;
|
||||
else if (item.is_loaded)
|
||||
item.status = PluginStatus::Activated;
|
||||
else
|
||||
item.status = PluginStatus::Inactive;
|
||||
item.status = resolve_plugin_status(item.loading, item.has_error, item.is_loaded);
|
||||
|
||||
item.available_actions = evaluate_action_policy(item);
|
||||
const bool has_enabled_script = std::any_of(item.capabilities.begin(), item.capabilities.end(),
|
||||
@@ -790,6 +784,9 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Plugin unloaded from Plugins dialog: " << plugin_key;
|
||||
// A user-disabled plugin has no meaningful error state.
|
||||
if (!manager.clear_plugin_error(plugin_key))
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to clear plugin error for " << plugin_key << " (failed to find)";
|
||||
// A prior activation of this plugin is moot now; drop it so no stale "Activated" arrives later.
|
||||
if (m_activating_plugin_key == plugin_key)
|
||||
m_activating_plugin_key.clear();
|
||||
|
||||
@@ -63,6 +63,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
|
||||
case PrintStatusRackReading: return "PrintStatusRackReading";
|
||||
case PrintStatusRackNozzleNumUnmeetWarning: return "PrintStatusRackNozzleNumUnmeetWarning";
|
||||
case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning";
|
||||
case PrintStatusOptionalPrinterModel: return "PrintStatusOptionalPrinterModel";
|
||||
case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch";
|
||||
case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC";
|
||||
case PrintStatusTPUUnsupportCaliOn: return "PrintStatusTPUUnsupportCaliOn";
|
||||
@@ -104,6 +105,7 @@ wxString PrePrintChecker::get_pre_state_msg(PrintDialogStatus status)
|
||||
case PrintStatusNeedConsistencyUpgrading: return _L("Cannot send the print job to a printer whose firmware must be updated.");
|
||||
case PrintStatusBlankPlate: return _L("Cannot send a print job for an empty plate.");
|
||||
case PrintStatusTimelapseNoSdcard: return _L("Storage needs to be inserted to record timelapse.");
|
||||
case PrintStatusOptionalPrinterModel: return _L("The selected printer model could not be identified, so compatibility with the print file configuration cannot be verified. Please verify the printer preset before sending.");
|
||||
case PrintStatusMixAmsAndVtSlotWarning: return _L("You have selected both external and AMS filaments for an extruder. You will need to manually switch the external filament during printing.");
|
||||
case PrintStatusTPUUnsupportAutoCali: return _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration.");
|
||||
case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value.");
|
||||
@@ -379,4 +381,3 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& infos)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ enum PrintDialogStatus : unsigned int {
|
||||
// Orca: a nozzle diameter that differs from the one the printer remembers is a warning,
|
||||
// not an error, so non-standard nozzles can still be printed with.
|
||||
PrintStatusNozzleDiameterMismatch,
|
||||
PrintStatusOptionalPrinterModel,
|
||||
PrintStatusPrinterWarningEnd,
|
||||
|
||||
// Warnings for filament
|
||||
@@ -170,7 +171,7 @@ public:
|
||||
void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback);
|
||||
static ::std::string get_print_status_info(PrintDialogStatus status);
|
||||
|
||||
wxString get_pre_state_msg(PrintDialogStatus status);
|
||||
static wxString get_pre_state_msg(PrintDialogStatus status);
|
||||
static bool is_error(PrintDialogStatus status) { return (PrintStatusErrorBegin < status) && (PrintStatusErrorEnd > status); };
|
||||
static bool is_error_printer(PrintDialogStatus status) { return (PrintStatusPrinterErrorBegin < status) && (PrintStatusPrinterErrorEnd > status); };
|
||||
static bool is_error_filament(PrintDialogStatus status) { return (PrintStatusFilamentErrorBegin < status) && (PrintStatusFilamentErrorEnd > status); };
|
||||
|
||||
@@ -361,7 +361,8 @@ wxString PresetComboBox::get_preset_item_name(unsigned int index)
|
||||
return GetString(index);
|
||||
}
|
||||
|
||||
std::map<std::string, MachineObject *> machine_list = dev->get_my_machine_list();
|
||||
std::map<std::string, MachineObject *> machine_list =
|
||||
dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
if (machine_list.empty()) {
|
||||
assert(false);
|
||||
m_selected_dev_id.clear();
|
||||
@@ -479,7 +480,8 @@ void PresetComboBox::add_connected_printers(std::string selected, bool alias_nam
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
std::map<std::string, MachineObject *> machine_list = dev->get_my_machine_list();
|
||||
std::map<std::string, MachineObject *> machine_list =
|
||||
dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
if (machine_list.empty())
|
||||
return;
|
||||
|
||||
@@ -999,7 +1001,13 @@ void PlaterPresetComboBox::update_badge_according_flag() {
|
||||
auto selection = GetSelection();
|
||||
auto select_flag = GetFlag(selection);
|
||||
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
|
||||
ShowBadge(ok);
|
||||
ShowBadge(m_sync_badge || ok);
|
||||
}
|
||||
|
||||
void PlaterPresetComboBox::set_sync_badge(bool show)
|
||||
{
|
||||
m_sync_badge = show;
|
||||
ShowBadge(show);
|
||||
}
|
||||
|
||||
bool PlaterPresetComboBox::switch_to_tab()
|
||||
|
||||
@@ -205,6 +205,7 @@ public:
|
||||
void msw_rescale() override;
|
||||
void OnSelect(wxCommandEvent& evt) override;
|
||||
void update_badge_according_flag();
|
||||
void set_sync_badge(bool show);
|
||||
|
||||
FilamentColor get_cur_color_info();
|
||||
void show_default_color_picker();
|
||||
@@ -214,6 +215,7 @@ public:
|
||||
private:
|
||||
// BBS
|
||||
wxColor m_color;
|
||||
bool m_sync_badge{false};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <miniz.h>
|
||||
#include <algorithm>
|
||||
#include "Plater.hpp"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
#include "md4c/src/md4c-html.h"
|
||||
#include "../Utils/Http.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -1461,6 +1463,45 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent)
|
||||
m_input_top_sizer->Add(0, 0, 0, wxTOP, FromDIP(4));
|
||||
m_input_top_sizer->Add(m_input_area, 0, wxRIGHT | wxEXPAND, FromDIP(18));
|
||||
|
||||
m_tips_cafile = new Label(ip_input_top_panel, _L("HTTPS CA File"));
|
||||
m_input_cafile = new wxTextCtrl(ip_input_top_panel, wxID_ANY);
|
||||
m_input_cafile->SetMinSize(wxSize(FromDIP(260), FromDIP(28)));
|
||||
m_input_cafile->SetMaxSize(wxSize(FromDIP(260), FromDIP(28)));
|
||||
|
||||
m_button_cafile = new Button(ip_input_top_panel, _L("Browse") + " " + dots);
|
||||
m_button_cafile->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
|
||||
m_button_cafile->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*");
|
||||
wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks,
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (openFileDialog.ShowModal() != wxID_CANCEL)
|
||||
m_input_cafile->SetValue(openFileDialog.GetPath());
|
||||
});
|
||||
|
||||
auto cafile_input_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
cafile_input_sizer->Add(m_input_cafile, 1, wxALIGN_CENTER_VERTICAL);
|
||||
cafile_input_sizer->Add(m_button_cafile, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
|
||||
|
||||
m_cafile_hint = new Label(ip_input_top_panel, _L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."));
|
||||
m_cafile_hint->Wrap(FromDIP(352));
|
||||
|
||||
m_input_top_sizer->Add(m_tips_cafile, 0, wxTOP | wxEXPAND, FromDIP(10));
|
||||
m_input_top_sizer->Add(cafile_input_sizer, 0, wxTOP | wxEXPAND, FromDIP(4));
|
||||
m_input_top_sizer->Add(m_cafile_hint, 0, wxTOP | wxEXPAND, FromDIP(4));
|
||||
|
||||
if (!Http::ca_file_supported()) {
|
||||
m_input_cafile->Disable();
|
||||
m_button_cafile->Disable();
|
||||
m_cafile_hint->SetLabel(_L("This system uses HTTPS certificates from the system Certificate Store or Keychain. To use a custom CA file, import it there."));
|
||||
m_cafile_hint->Wrap(FromDIP(352));
|
||||
}
|
||||
|
||||
if (wxGetApp().preset_bundle) {
|
||||
const auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printhost_cafile"))
|
||||
m_input_cafile->SetValue(from_u8(config.opt_string("printhost_cafile")));
|
||||
}
|
||||
|
||||
ip_input_top_panel->SetSizer(m_input_top_sizer);
|
||||
ip_input_top_panel->Layout();
|
||||
ip_input_top_panel->Fit();
|
||||
@@ -1736,7 +1777,8 @@ void InputIpAddressDialog::set_machine_obj(MachineObject* obj)
|
||||
auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
|
||||
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
|
||||
// ORCA enabling / disabling buttons with conditions enough to change its style
|
||||
m_button_ok->Enable(isIp(str_ip.ToStdString()) && str_access_code.Length() == 8);
|
||||
m_button_ok->Enable(isIp(str_ip.ToStdString()) &&
|
||||
(str_access_code.IsEmpty() || str_access_code.Length() >= 8));
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
@@ -1801,6 +1843,8 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt)
|
||||
m_trouble_shoot->Hide();
|
||||
std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString();
|
||||
std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString();
|
||||
if (str_access_code.empty())
|
||||
str_access_code = "88888888";
|
||||
std::string str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both).ToStdString();
|
||||
// Serial number should not contain lower case letters, and bambu_network plugin crashes
|
||||
// if user entered the wrong serial number, so we call `Upper()` here.
|
||||
@@ -1820,6 +1864,17 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt)
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
if (wxGetApp().preset_bundle) {
|
||||
auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
std::string port;
|
||||
Http::get_host_from_url(str_ip, &port);
|
||||
config.opt_string("print_host") = str_ip;
|
||||
if (!port.empty())
|
||||
config.opt_string("printhost_port") = port;
|
||||
if (Http::ca_file_supported())
|
||||
config.opt_string("printhost_cafile") = m_input_cafile->GetValue().ToStdString();
|
||||
}
|
||||
|
||||
token_.reset(this, nop_deleter);
|
||||
m_thread = new boost::thread(boost::bind(&InputIpAddressDialog::workerThreadFunc, this, str_ip, str_access_code, str_sn, str_model_id, str_name));
|
||||
}
|
||||
@@ -1835,6 +1890,8 @@ void InputIpAddressDialog::on_send_retry()
|
||||
Fit();
|
||||
wxString ip = m_input_ip->GetTextCtrl()->GetValue();
|
||||
wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
|
||||
if (str_access_code.IsEmpty())
|
||||
str_access_code = "88888888";
|
||||
|
||||
// check support function
|
||||
if (!m_obj) return;
|
||||
@@ -2056,7 +2113,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
|
||||
auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
|
||||
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
|
||||
|
||||
if (str_access_code.empty()) {
|
||||
if (str_access_code.IsEmpty()) {
|
||||
str_access_code = "88888888";
|
||||
}
|
||||
|
||||
@@ -2072,7 +2129,8 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
|
||||
}
|
||||
|
||||
// ORCA enabling / disabling buttons with conditions enough to change its style
|
||||
bool enable_btns = isIp(str_ip.ToStdString()) && str_access_code.Length() == 8 && invalid_access_code;
|
||||
bool valid_access_code_length = str_access_code.IsEmpty() || str_access_code.Length() >= 8;
|
||||
bool enable_btns = isIp(str_ip.ToStdString()) && valid_access_code_length && invalid_access_code;
|
||||
m_button_manual_setup->Enable(enable_btns);
|
||||
m_button_ok->Enable(enable_btns);
|
||||
|
||||
@@ -2087,7 +2145,7 @@ InputIpAddressDialog::~InputIpAddressDialog()
|
||||
|
||||
void InputIpAddressDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
|
||||
m_button_cafile->Rescale();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -315,12 +315,16 @@ public:
|
||||
Button* m_button_manual_setup{ nullptr };
|
||||
Label* m_tips_ip{ nullptr };
|
||||
Label* m_tips_access_code{ nullptr };
|
||||
Label* m_tips_cafile{ nullptr };
|
||||
Label* m_cafile_hint{ nullptr };
|
||||
Label* m_tips_sn{nullptr};
|
||||
Label* m_tips_modelID{nullptr};
|
||||
Label* m_test_right_msg{ nullptr };
|
||||
Label* m_test_wrong_msg{ nullptr };
|
||||
TextInput* m_input_ip{ nullptr };
|
||||
TextInput* m_input_access_code{ nullptr };
|
||||
wxTextCtrl* m_input_cafile{ nullptr };
|
||||
Button* m_button_cafile{ nullptr };
|
||||
TextInput* m_input_printer_name{ nullptr };
|
||||
TextInput* m_input_sn{ nullptr };
|
||||
ComboBox* m_input_modelID{ nullptr };
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
|
||||
#include "DeviceCore/DevConfig.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
#include "DeviceCore/DevNozzleRack.h"
|
||||
#include "DeviceCore/DevExtensionTool.h"
|
||||
@@ -1128,7 +1129,10 @@ bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams)
|
||||
|
||||
int filament_result = 0;
|
||||
std::vector<bool> map_opt; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
|
||||
if (nozzle_nums > 1){
|
||||
// Orca: only do the per-physical-extruder left/right split when the device actually reports
|
||||
// 2+ extruders. A non-BBL multi-nozzle printer (e.g. Snapmaker U1) reports a single extruder
|
||||
// with one filament pool, so it maps as a single surface via the else branch below.
|
||||
if (nozzle_nums > 1 && obj_->GetExtderSystem()->GetTotalExtderCount() > 1){
|
||||
//get nozzle property, the extders are same?
|
||||
if (true/*!can_hybrid_mapping(obj_get_extder_data())*/){
|
||||
std::vector<FilamentInfo> m_ams_mapping_result_left, m_ams_mapping_result_right;
|
||||
@@ -2287,9 +2291,7 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
||||
// Fill the real per-printer max color count into the %s template.
|
||||
if (!params.empty())
|
||||
msg = wxString::Format(m_pre_print_checker.get_pre_state_msg(status), params[0], params[0]);
|
||||
}
|
||||
|
||||
else if (status == PrintDialogStatus::PrintStatusAmsMappingU0Invalid) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusAmsMappingU0Invalid) {
|
||||
wxString msg_text;
|
||||
if (params.size() > 1)
|
||||
msg_text = wxString::Format(_L("Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."), params[0], params[1]);
|
||||
@@ -2315,8 +2317,10 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
||||
} else if (status == PrintDialogStatus::PrintStatusNoSdcard) {
|
||||
Enable_Refresh_Button(true);
|
||||
Enable_Send_Button(false);
|
||||
}else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter ||
|
||||
status == PrintDialogStatus::PrintStatusOptionalPrinterModel) {
|
||||
wxString msg_text;
|
||||
const bool block_send = status == PrintDialogStatus::PrintStatusUnsupportedPrinter;
|
||||
try
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
@@ -2342,17 +2346,21 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
||||
|
||||
auto target_print_name = wxString(DevPrinterConfigUtil::get_printer_display_name(target_model_id));
|
||||
target_print_name.Replace(wxT("Bambu Lab "), wxEmptyString);
|
||||
msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the print file configuration (%s). Please adjust the printer preset in the prepare page or choose a compatible printer on this page."), sourcet_print_name, target_print_name);
|
||||
if (block_send) {
|
||||
msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the print file configuration (%s). Please adjust the printer preset in the prepare page or choose a compatible printer on this page."), sourcet_print_name, target_print_name);
|
||||
} else {
|
||||
msg_text = wxString::Format(_L("The selected printer (%s) has an unknown model, so compatibility with the print file configuration (%s) cannot be verified. Please verify the printer preset before sending."), sourcet_print_name, target_print_name);
|
||||
}
|
||||
|
||||
|
||||
msg = msg_text;
|
||||
Enable_Refresh_Button(true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Send_Button(!block_send);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Enable_Refresh_Button(true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Send_Button(!block_send);
|
||||
}
|
||||
|
||||
|
||||
@@ -2513,31 +2521,13 @@ void SelectMachineDialog::on_cancel(wxCloseEvent &event)
|
||||
|
||||
bool SelectMachineDialog::is_blocking_printing(MachineObject* obj_)
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
auto target_model = obj_->printer_type;
|
||||
std::string source_model = "";
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL)
|
||||
return wxGetApp().is_blocking_printing(obj_);
|
||||
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
|
||||
|
||||
}else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) {
|
||||
if (m_required_data_plate_data_list.size() > 0) {
|
||||
source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
std::string source_model;
|
||||
if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty())
|
||||
source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id;
|
||||
return wxGetApp().is_blocking_printing(obj_, source_model);
|
||||
}
|
||||
|
||||
static std::unordered_set<int> _get_used_nozzle_idxes()
|
||||
@@ -2625,6 +2615,10 @@ bool SelectMachineDialog::is_same_printer_model()
|
||||
if(preset_bundle == nullptr) return result;
|
||||
const auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
const auto target_model = obj_->printer_type;
|
||||
if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(target_model)) {
|
||||
return true;
|
||||
}
|
||||
// Orca: ignore P1P -> P1S
|
||||
if (source_model != target_model) {
|
||||
if ((source_model == "C12" && target_model == "C11") || (source_model == "C11" && target_model == "C12") ||
|
||||
@@ -3699,10 +3693,32 @@ void SelectMachineDialog::on_send_print()
|
||||
m_print_job->on_success([this]() { finish_mode(); });
|
||||
|
||||
m_print_job->on_check_ip_address_fail([this]() {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
|
||||
wxQueueEvent(this, evt);
|
||||
wxGetApp().show_ip_address_enter_dialog();
|
||||
});
|
||||
// Invoked from the PrintJob worker thread when the LAN pre-flight (file upload
|
||||
// verification) fails. Marshal device/UI access to the main thread.
|
||||
CallAfter([this]()
|
||||
{
|
||||
// Reset the dialog out of sending mode so the user can retry.
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
|
||||
wxQueueEvent(this, evt);
|
||||
|
||||
DeviceManager* dev = wxGetApp().getDeviceManager();
|
||||
MachineObject* obj = dev ? dev->get_selected_machine() : nullptr;
|
||||
|
||||
if (obj && obj->is_connected())
|
||||
{
|
||||
// Connected: failed on file upload
|
||||
MessageDialog dlg(this,
|
||||
_L("Failed to upload the file to the printer's storage. Please try again."),
|
||||
_L("Send Failed"), wxOK | wxICON_ERROR);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not connected: reenter ip and access code
|
||||
wxGetApp().show_ip_address_enter_dialog();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// update ota version
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
@@ -4536,11 +4552,13 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj
|
||||
|
||||
// check nozzle data valid
|
||||
{
|
||||
if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine ||
|
||||
installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) {
|
||||
show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid);
|
||||
return false;
|
||||
}
|
||||
// Commented out the following as ntUndefine and 0.0f are default values
|
||||
// (signifying that the value is not given) that should PASS, not fail
|
||||
// if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine ||
|
||||
// installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) {
|
||||
// show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (obj_->is_nozzle_flow_type_supported() &&
|
||||
installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) {
|
||||
@@ -4569,7 +4587,10 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj
|
||||
|
||||
// check nozzle diameter
|
||||
{
|
||||
if (slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) {
|
||||
// 0.0f is default when there is no nozzle diameter is given.
|
||||
// In nozzle_diameter == 0.0f case, it passes and does not require a comparison
|
||||
if (installed_ext_nozzle.GetNozzleDiameter() > 0.0f &&
|
||||
slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) {
|
||||
std::vector<wxString> msg_params;
|
||||
if (ext_sys->GetTotalExtderCount() == 2) {
|
||||
const wxString& mismatch_nozzle_str = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx);
|
||||
@@ -4763,6 +4784,22 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
has_optional_printer_model = has_optional_printer_model ||
|
||||
(preset_bundle && DevPrinterConfigUtil::is_optional_printer_model_id(
|
||||
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)));
|
||||
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) {
|
||||
has_optional_printer_model = has_optional_printer_model ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(
|
||||
m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id);
|
||||
}
|
||||
|
||||
if (has_optional_printer_model) {
|
||||
show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel);
|
||||
}
|
||||
|
||||
if (is_blocking_printing(obj_)) {
|
||||
show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter);
|
||||
return;
|
||||
@@ -5485,8 +5522,8 @@ void SelectMachineDialog::reset_and_sync_ams_list()
|
||||
item = new MaterialItem(m_filament_left_panel, colour_rgb, _L(display_materials[extruder]));
|
||||
m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5));
|
||||
}
|
||||
else if (m_filaments_map[extruder] == 2)
|
||||
{
|
||||
else // map == 2, or (non-BBL multi-nozzle) 3+; update_material_item_pos() collapses
|
||||
{ // these into the single panel when the device reports < 2 extruders.
|
||||
item = new MaterialItem(m_filament_right_panel, colour_rgb, _L(display_materials[extruder]));
|
||||
m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5));
|
||||
}
|
||||
|
||||
@@ -738,7 +738,7 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
|
||||
if (obj && obj->is_online() && !obj->can_abort() && !obj->is_in_upgrading() && it->second->get_state_selected() == 1 && it->second->state_printable <= 2) {
|
||||
|
||||
if (!it->second->is_blocking_printing(obj)) {
|
||||
if (!wxGetApp().is_blocking_printing(obj)) {
|
||||
PrintParams params = request_params(obj);
|
||||
print_params.push_back(params);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "BitmapCache.hpp"
|
||||
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
#include "slic3r/Utils/FileTransferUtils.hpp"
|
||||
|
||||
@@ -300,7 +301,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
|
||||
m_storage_panel->Layout();
|
||||
|
||||
// try to connect
|
||||
m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
|
||||
m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(400), -1), wxALIGN_CENTER_HORIZONTAL);
|
||||
m_statictext_printer_msg->SetFont(::Label::Body_13);
|
||||
m_statictext_printer_msg->SetForegroundColour(*wxBLACK);
|
||||
m_statictext_printer_msg->Hide();
|
||||
@@ -760,9 +761,25 @@ void SendToPrinterDialog::update_priner_status_msg(wxString msg, bool is_warning
|
||||
if (str_new != str_old) {
|
||||
if (m_statictext_printer_msg->GetLabel() != msg) {
|
||||
m_statictext_printer_msg->SetLabel(msg);
|
||||
m_statictext_printer_msg->SetMinSize(wxSize(FromDIP(400), -1));
|
||||
m_statictext_printer_msg->SetMaxSize(wxSize(FromDIP(400), -1));
|
||||
m_statictext_printer_msg->Wrap(FromDIP(400));
|
||||
const int wrap_width = FromDIP(400);
|
||||
m_statictext_printer_msg->Wrap(wrap_width);
|
||||
int line_count = 1;
|
||||
const wxString wrapped_label = m_statictext_printer_msg->GetLabel();
|
||||
for (size_t i = 0; i < wrapped_label.length(); ++i) {
|
||||
if (wrapped_label[i] == '\n')
|
||||
++line_count;
|
||||
}
|
||||
wxCoord text_width = 0;
|
||||
wxCoord text_height = 0;
|
||||
m_statictext_printer_msg->GetTextExtent(msg, &text_width, &text_height);
|
||||
const int extent_line_count = text_width > 0 ?
|
||||
std::max(1, (static_cast<int>(text_width) + wrap_width - 1) / wrap_width) : 1;
|
||||
line_count = std::max(line_count, extent_line_count);
|
||||
const int line_height = std::max(m_statictext_printer_msg->GetCharHeight(), static_cast<int>(text_height));
|
||||
const int min_height = std::max(m_statictext_printer_msg->GetBestSize().GetHeight(),
|
||||
line_count * line_height + FromDIP(2));
|
||||
m_statictext_printer_msg->SetMinSize(wxSize(wrap_width, min_height));
|
||||
m_statictext_printer_msg->SetMaxSize(wxDefaultSize);
|
||||
m_statictext_printer_msg->Show();
|
||||
Layout();
|
||||
Fit();
|
||||
@@ -1102,7 +1119,7 @@ void SendToPrinterDialog::update_user_printer()
|
||||
wxArrayString machine_list_name;
|
||||
std::map<std::string, MachineObject*> option_list;
|
||||
|
||||
option_list = dev->get_my_machine_list();
|
||||
option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
|
||||
// same machine only appear once
|
||||
for (auto it = option_list.begin(); it != option_list.end(); it++) {
|
||||
@@ -1260,7 +1277,7 @@ void SendToPrinterDialog::update_show_status()
|
||||
reset_timeout();
|
||||
|
||||
// reading done
|
||||
if (is_blocking_printing(obj_)) {
|
||||
if (wxGetApp().is_blocking_printing(obj_)) {
|
||||
show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter);
|
||||
return;
|
||||
}
|
||||
@@ -1325,26 +1342,6 @@ void SendToPrinterDialog::update_show_status()
|
||||
}
|
||||
}
|
||||
|
||||
bool SendToPrinterDialog::is_blocking_printing(MachineObject* obj_)
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
auto target_model = obj_->printer_type;
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SendToPrinterDialog::Enable_Refresh_Button(bool en)
|
||||
{
|
||||
if (!en) {
|
||||
@@ -1413,79 +1410,68 @@ void SendToPrinterDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
||||
update_print_status_msg(wxEmptyString, false, false);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusInvalidPrinter) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusInvalidPrinter) {
|
||||
update_print_status_msg(wxEmptyString, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusConnectingServer) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusConnectingServer) {
|
||||
wxString msg_text = _L("Connecting to server...");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(true);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusReading) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusReading) {
|
||||
wxString msg_text = _L("Synchronizing device information...");
|
||||
update_print_status_msg(msg_text, false, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(false);
|
||||
}
|
||||
|
||||
else if (status == PrintDialogStatus::PrintStatusReadingFinished) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusReadingFinished) {
|
||||
update_print_status_msg(wxEmptyString, false, true);
|
||||
Enable_Send_Button(true);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusReadingTimeout) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusReadingTimeout) {
|
||||
wxString msg_text = _L("Synchronizing device information timed out.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(true);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusInUpgrading) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusInUpgrading) {
|
||||
wxString msg_text = _L("Cannot send print tasks when an update is in progress");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter) {
|
||||
wxString msg_text = _L("The selected printer is incompatible with the chosen printer presets.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Send_Button(true);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusRefreshingMachineList) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusRefreshingMachineList) {
|
||||
update_print_status_msg(wxEmptyString, false, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(false);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusSending) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusSending) {
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(false);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusSendingCanceled) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusSendingCanceled) {
|
||||
Enable_Send_Button(true);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusNoSdcard) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusNoSdcard) {
|
||||
wxString msg_text = _L("Storage needs to be inserted before send to printer.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusNotOnTheSameLAN) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusNotOnTheSameLAN) {
|
||||
wxString msg_text = _L("The printer is required to be on the same LAN as Orca Slicer.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
}
|
||||
else if (status == PrintDialogStatus::PrintStatusNotSupportedSendToSDCard) {
|
||||
} else if (status == PrintDialogStatus::PrintStatusNotSupportedSendToSDCard) {
|
||||
wxString msg_text = _L("The printer does not support sending to printer storage.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
} else if (status == PrintDialogStatus::PrintStatusPublicInitFailed) {
|
||||
wxString msg_text = _L(
|
||||
"Failed to initialize the printer file transfer. Please check the connection and try again.");
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
Enable_Send_Button(false);
|
||||
Enable_Refresh_Button(true);
|
||||
} else if (status == PrintDialogStatus::PrintStatusPublicUploadFiled) {
|
||||
@@ -1663,30 +1649,18 @@ extern void refresh_agora_url(char const *device, char const *dev_ver, char
|
||||
void SendToPrinterDialog::GetConnection()
|
||||
{
|
||||
DeviceManager *dm = GUI::wxGetApp().getDeviceManager();
|
||||
MachineObject *obj = dm ? dm->get_selected_machine() : nullptr;
|
||||
|
||||
MachineObject *obj = dm->get_selected_machine();
|
||||
if (obj == nullptr) {
|
||||
if (!obj)
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : obj is empty";
|
||||
m_connection_status = ConnectionStatus::NOT_START;
|
||||
}
|
||||
|
||||
int remote_proto = obj->get_file_remote();
|
||||
if (!remote_proto) {
|
||||
if (obj && !obj->get_file_remote())
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : remote_proto is not support";
|
||||
m_connection_status = ConnectionStatus::NOT_START;
|
||||
}
|
||||
if (obj && obj->is_camera_busy_off())
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy";
|
||||
|
||||
if (obj->is_camera_busy_off()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy";
|
||||
m_connection_status = ConnectionStatus::NOT_START;
|
||||
}
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
|
||||
NetworkAgent *agent = wxGetApp().getAgent();
|
||||
std::string agent_version = agent ? agent->get_version() : "";
|
||||
std::string dev_ver = obj->get_ota_version();
|
||||
std::string dev_id = obj->get_dev_id();
|
||||
|
||||
if (m_url_timer && m_url_timer->IsRunning())
|
||||
if (m_url_timer && m_url_timer->IsRunning())
|
||||
{
|
||||
m_url_timer->Stop();
|
||||
}
|
||||
@@ -1709,19 +1683,40 @@ void SendToPrinterDialog::GetConnection()
|
||||
m_url_timer->GetId());
|
||||
m_url_timer->StartOnce(8000);
|
||||
|
||||
if (agent) {
|
||||
if (obj && agent)
|
||||
{
|
||||
std::string dev_ver = obj->get_ota_version();
|
||||
std::string dev_id = obj->get_dev_id();
|
||||
|
||||
if (m_tcp_try_connect) {
|
||||
std::string devIP = obj->get_dev_ip();
|
||||
std::string accessCode = obj->get_access_code();
|
||||
std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp";
|
||||
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
|
||||
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
|
||||
CallAfter([this, is_success, err_code, error_msg]() {
|
||||
OnConnection(is_success, err_code, error_msg);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp, dev_id=" << dev_id
|
||||
<< ", dev_ip=" << devIP << ", access_code_len=" << accessCode.size();
|
||||
|
||||
try
|
||||
{
|
||||
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
|
||||
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg)
|
||||
{
|
||||
CallAfter([this, is_success, err_code, error_msg]()
|
||||
{
|
||||
OnConnection(is_success, err_code, error_msg);
|
||||
});
|
||||
});
|
||||
});
|
||||
m_filetransfer_tunnel->start_connect();
|
||||
m_filetransfer_tunnel->start_connect();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tcp FileTransferTunnel unavailable for dev_id=" <<
|
||||
dev_id
|
||||
<< " dev_ip=" << devIP << ": " << e.what();
|
||||
if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop();
|
||||
m_filetransfer_tunnel.reset();
|
||||
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
|
||||
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
|
||||
}
|
||||
}
|
||||
else if (m_tutk_try_connect)
|
||||
{
|
||||
@@ -1749,11 +1744,28 @@ void SendToPrinterDialog::GetConnection()
|
||||
if (boost::algorithm::starts_with(url, "bambu:///"))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk";
|
||||
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
|
||||
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
|
||||
CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); });
|
||||
});
|
||||
m_filetransfer_tunnel->start_connect();
|
||||
try
|
||||
{
|
||||
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
|
||||
m_filetransfer_tunnel->on_connection(
|
||||
[this](bool is_success, int err_code, std::string error_msg)
|
||||
{
|
||||
CallAfter([this, is_success, err_code, error_msg]()
|
||||
{
|
||||
OnConnection(is_success, err_code, error_msg);
|
||||
});
|
||||
});
|
||||
m_filetransfer_tunnel->start_connect();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tutk FileTransferTunnel unavailable: " << e.
|
||||
what();
|
||||
if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop();
|
||||
m_filetransfer_tunnel.reset();
|
||||
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
|
||||
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1852,8 +1864,17 @@ void SendToPrinterDialog::ResetTunnelAndJob()
|
||||
|
||||
void SendToPrinterDialog::CreateMediaAbilityJob()
|
||||
{
|
||||
nlohmann::json media_ability = {{"cmd_type", 7}};
|
||||
m_filetransfer_mediability_job = std::make_unique<FileTransferJob>(module(), std::string(media_ability.dump()));
|
||||
nlohmann::json media_ability = {{"cmd_type", 7}};
|
||||
try
|
||||
{
|
||||
m_filetransfer_mediability_job = std::make_unique<FileTransferJob>(module(), std::string(media_ability.dump()));
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what();
|
||||
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
|
||||
return;
|
||||
}
|
||||
m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) {
|
||||
//this pl
|
||||
CallAfter([this, res, resp_ec, json_res] {
|
||||
@@ -1908,11 +1929,20 @@ void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std
|
||||
{"cmd_type", 5},
|
||||
};
|
||||
upload_params["dest_storage"] = m_selected_storage;
|
||||
upload_params["dest_name"] = name; // filenme no path
|
||||
upload_params["file_path"] = path;
|
||||
upload_params["dest_name"] = name; // filenme no path
|
||||
upload_params["file_path"] = path;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob";
|
||||
m_filetransfer_uploadfile_job = std::make_unique<FileTransferJob>(module(), std::string(upload_params.dump()));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob";
|
||||
try
|
||||
{
|
||||
m_filetransfer_uploadfile_job = std::make_unique<FileTransferJob>(module(), std::string(upload_params.dump()));
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what();
|
||||
show_status(PrintDialogStatus::PrintStatusPublicUploadFiled);
|
||||
return;
|
||||
}
|
||||
m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) { //
|
||||
CallAfter([this, res, resp_ec, json_res, bin_res] {
|
||||
UploadFileRessultCallback(res, resp_ec,json_res, bin_res);
|
||||
|
||||
@@ -181,7 +181,6 @@ public:
|
||||
void reset_timeout();
|
||||
void update_user_printer();
|
||||
void update_show_status();
|
||||
bool is_blocking_printing(MachineObject* obj_);
|
||||
void prepare(int print_plate_idx);
|
||||
void check_focus(wxWindow* window);
|
||||
void check_fcous_state(wxWindow* window);
|
||||
|
||||
+106
-105
@@ -22,6 +22,7 @@
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/sstream.h>
|
||||
#include <wx/zstream.h>
|
||||
#include <chrono>
|
||||
|
||||
#include "DeviceCore/DevBed.h"
|
||||
#include "DeviceCore/DevCtrl.h"
|
||||
@@ -1501,27 +1502,26 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||
|
||||
m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
||||
m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||
m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||
m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
||||
m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
|
||||
m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
||||
const std::string js_request_pip = R"(
|
||||
document.querySelector('video').requestPictureInPicture();
|
||||
)";
|
||||
m_custom_camera_view->RunScript(js_request_pip);
|
||||
});
|
||||
m_camera_switch_button->Hide();
|
||||
// m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
||||
// m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||
// m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||
// m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
||||
// m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
||||
// const std::string js_request_pip = R"(
|
||||
// document.querySelector('video').requestPictureInPicture();
|
||||
// )";
|
||||
// m_custom_camera_view->RunScript(js_request_pip);
|
||||
// });
|
||||
// m_camera_switch_button->Hide();
|
||||
|
||||
m_bitmap_sdcard_img->SetToolTip(_L("Storage"));
|
||||
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
||||
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
||||
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
||||
m_setting_button->SetToolTip(_L("Camera Setting"));
|
||||
m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
||||
// m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
||||
|
||||
bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
// bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
@@ -1544,19 +1544,18 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
||||
m_custom_camera_view->EnableContextMenu(false);
|
||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
|
||||
m_web_media_controller = std::make_unique<WebMediaController>(m_custom_camera_view);
|
||||
|
||||
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
||||
m_media_play_ctrl->SetWebMediaController(m_web_media_controller.get());
|
||||
m_custom_camera_view->Hide();
|
||||
m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
||||
if (evt.GetString() == "leavepictureinpicture") {
|
||||
// When leaving PiP, video gets paused in some cases and toggling play
|
||||
// programmatically does not work.
|
||||
m_custom_camera_view->Reload();
|
||||
}
|
||||
else if (evt.GetString() == "enterpictureinpicture") {
|
||||
toggle_builtin_camera();
|
||||
}
|
||||
});
|
||||
// m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
||||
// if (evt.GetString() == "leavepictureinpicture") {
|
||||
// // When leaving PiP, video gets paused in some cases and toggling play
|
||||
// // programmatically does not work.
|
||||
// m_custom_camera_view->Reload();
|
||||
// }
|
||||
// });
|
||||
|
||||
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
||||
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
|
||||
@@ -1566,10 +1565,6 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||
//
|
||||
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
||||
|
||||
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
|
||||
handle_camera_source_change();
|
||||
}
|
||||
|
||||
return sizer;
|
||||
}
|
||||
|
||||
@@ -2333,6 +2328,27 @@ void StatusPanel::update_camera_state(MachineObject* obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
|
||||
auto agent = wxGetApp().getAgent();
|
||||
const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
|
||||
const bool use_webview = camera_mode == CameraStreamMode::http_snapshot;
|
||||
if (use_webview) {
|
||||
//m_camera_switch_button->Hide();
|
||||
if (!m_custom_camera_view->IsShown()) {
|
||||
// why: do not reload the WebView URL per tick, or redirects can cause a reload loop.
|
||||
// MediaPlayCtrl (via its WebMediaController) owns loading/playing the stream itself.
|
||||
m_custom_camera_view->Show();
|
||||
m_media_ctrl->Hide();
|
||||
}
|
||||
} else {
|
||||
if (m_custom_camera_view->IsShown()) {
|
||||
m_custom_camera_view->Hide();
|
||||
// Stop the snapshot WebView before switching to native playback
|
||||
// or leaving the camera mode.
|
||||
m_media_play_ctrl->StopWebStream();
|
||||
}
|
||||
m_media_ctrl->Show();
|
||||
}
|
||||
|
||||
//sdcard
|
||||
auto sdcard_state = obj->GetStorage()->get_sdcard_state();
|
||||
if (m_last_sdcard != sdcard_state) {
|
||||
@@ -2364,7 +2380,12 @@ void StatusPanel::update_camera_state(MachineObject* obj)
|
||||
m_last_recording = obj->is_recording() ? 1 : 0;
|
||||
}
|
||||
|
||||
if (!m_bitmap_recording_img->IsShown()) {
|
||||
if (use_webview) {
|
||||
if (m_bitmap_recording_img->IsShown()) {
|
||||
m_bitmap_recording_img->Hide();
|
||||
m_panel_monitoring_title->Layout();
|
||||
}
|
||||
} else if (!m_bitmap_recording_img->IsShown()) {
|
||||
m_bitmap_recording_img->Show();
|
||||
m_panel_monitoring_title->Layout();
|
||||
}
|
||||
@@ -2421,6 +2442,8 @@ void StatusPanel::update_camera_state(MachineObject* obj)
|
||||
bool show_vcamera = m_media_play_ctrl->IsStreaming();
|
||||
m_camera_popup->update(show_vcamera);
|
||||
}
|
||||
|
||||
m_setting_button->Show(!use_webview);
|
||||
}
|
||||
|
||||
StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
|
||||
@@ -3951,39 +3974,60 @@ void StatusPanel::update_cloud_subtask(MachineObject *obj)
|
||||
update_calib_bitmap();
|
||||
if (obj->slice_info) {
|
||||
m_request_url = wxString(obj->slice_info->thumbnail_url);
|
||||
if (!m_request_url.IsEmpty()) {
|
||||
wxImage img;
|
||||
std::map<wxString, wxImage>::iterator it = img_list.find(m_request_url);
|
||||
if (it != img_list.end()) {
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) {
|
||||
img = it->second;
|
||||
wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y);
|
||||
m_project_task_panel->set_thumbnail_img(resize_img, "");
|
||||
m_project_task_panel->set_brightness_value(get_brightness_value(resize_img));
|
||||
}
|
||||
if (this->obj) {
|
||||
m_project_task_panel->set_plate_index(obj->m_plate_index);
|
||||
} else {
|
||||
m_project_task_panel->set_plate_index(-1);
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL;
|
||||
BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image";
|
||||
} else {
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url;
|
||||
web_request.Start();
|
||||
m_start_loading_thumbnail = false;
|
||||
}
|
||||
}
|
||||
load_thumbnail_from_url(m_request_url, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StatusPanel::load_thumbnail_from_url(const wxString &url, MachineObject *obj)
|
||||
{
|
||||
if (url.IsEmpty())
|
||||
return false;
|
||||
|
||||
wxImage img;
|
||||
std::map<wxString, wxImage>::iterator it = img_list.find(url);
|
||||
if (it != img_list.end()) {
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) {
|
||||
img = it->second;
|
||||
wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y);
|
||||
m_project_task_panel->set_thumbnail_img(resize_img, "");
|
||||
m_project_task_panel->set_brightness_value(get_brightness_value(resize_img));
|
||||
}
|
||||
if (this->obj) {
|
||||
m_project_task_panel->set_plate_index(obj->m_plate_index);
|
||||
} else {
|
||||
m_project_task_panel->set_plate_index(-1);
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL;
|
||||
BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image";
|
||||
} else {
|
||||
m_request_url = url;
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url;
|
||||
web_request.Start();
|
||||
m_start_loading_thumbnail = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StatusPanel::update_sdcard_subtask(MachineObject *obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
|
||||
if (!m_load_sdcard_thumbnail) {
|
||||
const wxString thumbnail_url = wxString(obj->m_agent_thumbnail_url);
|
||||
if (!thumbnail_url.IsEmpty()) {
|
||||
if (m_request_url != thumbnail_url || !m_load_sdcard_thumbnail) {
|
||||
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
|
||||
web_request.Cancel();
|
||||
update_calib_bitmap();
|
||||
m_request_url = thumbnail_url;
|
||||
load_thumbnail_from_url(thumbnail_url, obj);
|
||||
m_load_sdcard_thumbnail = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_load_sdcard_thumbnail || !m_request_url.IsEmpty()) {
|
||||
update_calib_bitmap();
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION) {
|
||||
m_project_task_panel->get_bitmap_thumbnail()->SetBitmap(m_thumbnail_sdcard.bmp());
|
||||
@@ -3991,6 +4035,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj)
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::SDCARD_THUMBNAIL;
|
||||
m_load_sdcard_thumbnail = true;
|
||||
m_request_url.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4720,11 +4765,11 @@ void StatusPanel::on_ams_refresh_rfid(wxCommandEvent &event)
|
||||
try {
|
||||
if (!use_new_command) {
|
||||
int tray_index = atoi(curr_ams_id.c_str()) * 4 + atoi(slot_it->second->id.c_str());
|
||||
obj->command_ams_refresh_rfid(std::to_string(tray_index));
|
||||
obj->command_ams_refresh_rfid(-1, tray_index);
|
||||
}
|
||||
|
||||
if (use_new_command) {
|
||||
obj->command_ams_refresh_rfid2(stoi(curr_ams_id), stoi(curr_can_id));
|
||||
obj->command_ams_refresh_rfid(stoi(curr_ams_id), stoi(curr_can_id));
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
@@ -4990,7 +5035,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
||||
}
|
||||
sdcard_hint_dlg->on_show();
|
||||
});
|
||||
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
|
||||
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
||||
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
||||
wxSize sz = ctrl->GetSize();
|
||||
@@ -5002,54 +5046,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
|
||||
{
|
||||
handle_camera_source_change();
|
||||
}
|
||||
|
||||
void StatusBasePanel::handle_camera_source_change()
|
||||
{
|
||||
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
|
||||
if (enabled && !new_cam_url.empty()) {
|
||||
m_custom_camera_view->LoadURL(new_cam_url);
|
||||
toggle_custom_camera();
|
||||
m_camera_switch_button->Show();
|
||||
} else {
|
||||
toggle_builtin_camera();
|
||||
m_camera_switch_button->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::toggle_builtin_camera()
|
||||
{
|
||||
m_custom_camera_view->Hide();
|
||||
m_media_ctrl->Show();
|
||||
m_media_play_ctrl->Show();
|
||||
}
|
||||
|
||||
void StatusBasePanel::toggle_custom_camera()
|
||||
{
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
|
||||
if (enabled) {
|
||||
m_custom_camera_view->Show();
|
||||
m_media_ctrl->Hide();
|
||||
m_media_play_ctrl->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
|
||||
{
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
if (enabled && m_media_ctrl->IsShown()) {
|
||||
toggle_custom_camera();
|
||||
} else {
|
||||
toggle_builtin_camera();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::remove_controls()
|
||||
{
|
||||
const std::string js_cleanup_video_element = R"(
|
||||
@@ -5202,6 +5198,11 @@ bool StatusPanel::is_stage_list_info_changed(MachineObject *obj)
|
||||
void StatusPanel::set_default()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default";
|
||||
if (m_custom_camera_view->IsShown()) {
|
||||
m_custom_camera_view->Hide();
|
||||
m_media_ctrl->Show();
|
||||
m_media_play_ctrl->StopWebStream();
|
||||
}
|
||||
obj = nullptr;
|
||||
last_subtask = nullptr;
|
||||
last_tray_exist_bits = -1;
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <memory>
|
||||
#include "MediaPlayCtrl.h"
|
||||
#include "WebMediaController.hpp"
|
||||
#include "AMSSetting.hpp"
|
||||
#include "Calibration.hpp"
|
||||
#include "CalibrationWizardPage.hpp"
|
||||
@@ -440,7 +442,7 @@ protected:
|
||||
wxStaticBitmap *m_bitmap_sdcard_img;
|
||||
wxStaticBitmap *m_bitmap_static_use_time;
|
||||
wxStaticBitmap *m_bitmap_static_use_weight;
|
||||
wxStaticBitmap* m_camera_switch_button;
|
||||
// wxStaticBitmap* m_camera_switch_button;
|
||||
|
||||
|
||||
wxMediaCtrl3 * m_media_ctrl;
|
||||
@@ -462,6 +464,8 @@ protected:
|
||||
ScalableButton *m_button_abort;
|
||||
Button * m_button_clean;
|
||||
wxWebView * m_custom_camera_view{nullptr};
|
||||
std::unique_ptr<WebMediaController> m_web_media_controller;
|
||||
|
||||
wxSimplebook* m_extruder_book;
|
||||
std::vector<ExtruderImage *> m_extruderImage;
|
||||
|
||||
@@ -577,13 +581,8 @@ protected:
|
||||
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); }
|
||||
void on_camera_source_change(wxCommandEvent& event);
|
||||
void handle_camera_source_change();
|
||||
void remove_controls();
|
||||
void on_webview_navigating(wxWebViewEvent& evt);
|
||||
void on_camera_switch_toggled(wxMouseEvent& event);
|
||||
void toggle_custom_camera();
|
||||
void toggle_builtin_camera();
|
||||
|
||||
public:
|
||||
StatusBasePanel(wxWindow * parent,
|
||||
@@ -631,6 +630,7 @@ class StatusPanel : public StatusBasePanel
|
||||
private:
|
||||
friend class MonitorPanel;
|
||||
void wire_controls();
|
||||
bool load_thumbnail_from_url(const wxString &url, MachineObject *obj);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
|
||||
|
||||
@@ -1746,6 +1746,8 @@ void SyncAmsInfoDialog::show_status(PrintDialogStatus status, std::vector<wxStri
|
||||
update_print_status_msg(msg_text, true, true);
|
||||
} else if (status == PrintDialogStatus::PrintStatusAmsMappingSuccess) {
|
||||
update_print_status_msg(wxEmptyString, false, false);
|
||||
} else if (status == PrintDialogStatus::PrintStatusOptionalPrinterModel) {
|
||||
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true, true);
|
||||
} else if (status == PrintDialogStatus::PrintStatusAmsMappingInvalid) {
|
||||
update_print_status_msg(wxEmptyString, true, false);
|
||||
} else if (status == PrintDialogStatus::PrintStatusAmsMappingMixInvalid) {
|
||||
@@ -1865,26 +1867,13 @@ void SyncAmsInfoDialog::on_cancel(wxCloseEvent &event)
|
||||
|
||||
bool SyncAmsInfoDialog::is_blocking_printing(MachineObject *obj_)
|
||||
{
|
||||
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return true;
|
||||
auto target_model = obj_->printer_type;
|
||||
std::string source_model = "";
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL)
|
||||
return wxGetApp().is_blocking_printing(obj_);
|
||||
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
|
||||
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) {
|
||||
if (m_required_data_plate_data_list.size() > 0) { source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; }
|
||||
}
|
||||
|
||||
if (source_model != target_model) {
|
||||
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
|
||||
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
|
||||
if (it == compatible_machine.end()) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
std::string source_model;
|
||||
if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty())
|
||||
source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id;
|
||||
return wxGetApp().is_blocking_printing(obj_, source_model);
|
||||
}
|
||||
|
||||
bool SyncAmsInfoDialog::is_same_nozzle_type(std::string &filament_type, NozzleType &tag_nozzle_type)
|
||||
@@ -1934,7 +1923,13 @@ bool SyncAmsInfoDialog::is_same_printer_model()
|
||||
if (obj_ == nullptr) { return result; }
|
||||
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle && preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) != obj_->printer_type) {
|
||||
const std::string source_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string();
|
||||
if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preset_bundle && source_model != obj_->printer_type) {
|
||||
if ((obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) && (preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) == "C12")) {
|
||||
return true;
|
||||
}
|
||||
@@ -2135,7 +2130,7 @@ void SyncAmsInfoDialog::update_user_printer()
|
||||
std::map<std::string, MachineObject *> option_list;
|
||||
|
||||
// user machine list
|
||||
option_list = dev->get_my_machine_list();
|
||||
option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
|
||||
// same machine only appear once
|
||||
for (auto it = option_list.begin(); it != option_list.end(); it++) {
|
||||
@@ -2268,6 +2263,18 @@ void SyncAmsInfoDialog::update_show_status()
|
||||
|
||||
reset_timeout();
|
||||
|
||||
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) {
|
||||
has_optional_printer_model = has_optional_printer_model ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(
|
||||
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle));
|
||||
}
|
||||
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) {
|
||||
has_optional_printer_model = has_optional_printer_model ||
|
||||
DevPrinterConfigUtil::is_optional_printer_model_id(m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id);
|
||||
}
|
||||
|
||||
if (!obj_->GetConfig()->SupportPrintAllPlates() && m_print_plate_idx == PLATE_ALL_IDX) {
|
||||
show_status(PrintDialogStatus::PrintStatusNotSupportedPrintAll);
|
||||
return;
|
||||
@@ -2361,6 +2368,9 @@ void SyncAmsInfoDialog::update_show_status()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_optional_printer_model)
|
||||
show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel);
|
||||
}
|
||||
|
||||
bool SyncAmsInfoDialog::has_timelapse_warning()
|
||||
@@ -3210,6 +3220,7 @@ SyncAmsInfoDialog::~SyncAmsInfoDialog() {
|
||||
void SyncAmsInfoDialog::set_info(SyncInfo &info)
|
||||
{
|
||||
m_input_info = info;
|
||||
reinit_dialog();
|
||||
}
|
||||
|
||||
void SyncAmsInfoDialog::update_lan_machine_list()
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "WebMediaController.hpp"
|
||||
|
||||
#include <wx/webview.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
void initialize_webview(wxWebView* webview)
|
||||
{
|
||||
if (!webview)
|
||||
return;
|
||||
|
||||
webview->SetBackgroundColour(*wxBLACK);
|
||||
webview->SetPage("<html><head><style>html,body{margin:0;height:100%;background:#000;}</style></head><body></body></html>", "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebMediaController::WebMediaController(wxWebView* webview)
|
||||
: m_webview(webview)
|
||||
{
|
||||
initialize_webview(m_webview);
|
||||
}
|
||||
|
||||
void WebMediaController::Load(wxURI url)
|
||||
{
|
||||
m_url = url.BuildURI().ToStdString();
|
||||
}
|
||||
|
||||
void WebMediaController::set_mode(CameraStreamMode mode)
|
||||
{
|
||||
m_stream_mode = mode;
|
||||
}
|
||||
|
||||
void WebMediaController::Play()
|
||||
{
|
||||
if (!m_webview)
|
||||
return;
|
||||
|
||||
wxString url = wxString::FromUTF8(m_url);
|
||||
wxString html = "<html><head><style>"
|
||||
"html,body{margin:0;height:100%;background:#000;overflow:hidden;}"
|
||||
"img{width:100%;height:100%;object-fit:contain;display:block;}"
|
||||
"</style></head><body><img id=\"camera-frame\"";
|
||||
if (m_stream_mode == CameraStreamMode::http_snapshot) {
|
||||
html += " data-camera-url=\"" + url +
|
||||
"\"><script>"
|
||||
"const cameraFrame=document.getElementById('camera-frame');"
|
||||
"const cameraUrl=cameraFrame.dataset.cameraUrl;"
|
||||
"let cameraFrameLoading=false;"
|
||||
"function refreshCameraFrame(){"
|
||||
"if(cameraFrameLoading)return;"
|
||||
"cameraFrameLoading=true;"
|
||||
"const nextFrame=new Image();"
|
||||
"nextFrame.onload=function(){cameraFrame.src=nextFrame.src;cameraFrameLoading=false;};"
|
||||
"nextFrame.onerror=function(){cameraFrameLoading=false;};"
|
||||
"nextFrame.src=cameraUrl+(cameraUrl.indexOf('?')>=0?'&':'?')+'_orca_frame='+Date.now();"
|
||||
"}"
|
||||
"let cameraRefreshInterval=null;"
|
||||
"function stopCameraRefresh(){"
|
||||
"if(cameraRefreshInterval!==null){"
|
||||
"clearInterval(cameraRefreshInterval);"
|
||||
"cameraRefreshInterval=null;"
|
||||
"}"
|
||||
"}"
|
||||
"refreshCameraFrame();"
|
||||
"cameraRefreshInterval = setInterval(refreshCameraFrame,200);"
|
||||
"</script></body></html>";
|
||||
m_webview->SetPage(html, url);
|
||||
} else {
|
||||
// Load MJPEG streams as the top-level document. Some embedded WebView
|
||||
// backends buffer a multipart stream when it is used as an <img> resource,
|
||||
// which introduces noticeable live-view latency.
|
||||
m_webview->LoadURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
void WebMediaController::Stop()
|
||||
{
|
||||
if (m_webview) {
|
||||
m_webview->RunScript("if(typeof stopCameraRefresh==='function') stopCameraRefresh();");
|
||||
m_webview->Stop();
|
||||
m_webview->SetPage("", "about:blank");
|
||||
m_webview->ClearHistory();
|
||||
}
|
||||
m_url.clear();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <slic3r/GUI/IMediaController.hpp>
|
||||
#include <slic3r/Utils/IPrinterAgent.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
class wxWebView;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class WebMediaController : public IMediaController
|
||||
{
|
||||
public:
|
||||
explicit WebMediaController(wxWebView* webview);
|
||||
|
||||
void Load(wxURI url) override;
|
||||
|
||||
void set_mode(CameraStreamMode mode);
|
||||
|
||||
void Play() override;
|
||||
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
wxWebView* m_webview = nullptr;
|
||||
std::string m_url;
|
||||
CameraStreamMode m_stream_mode = CameraStreamMode::http;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "WebRtcMediaController.hpp"
|
||||
|
||||
#include <rtc/common.hpp>
|
||||
#include <rtc/rtc.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <wx/mstream.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace {
|
||||
void init_rtc_logger_once()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [] {
|
||||
rtc::InitLogger(rtc::LogLevel::Warning, [](rtc::LogLevel level, std::string message) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "[rtc:" << static_cast<int>(level) << "] " << message;
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
WebRtcMediaController::WebRtcMediaController(std::function<void(const wxImage&, wxSize)> frame_sink,
|
||||
std::function<void(Status)> on_status)
|
||||
: m_frame_sink(std::move(frame_sink))
|
||||
, m_on_status(std::move(on_status))
|
||||
{
|
||||
}
|
||||
|
||||
WebRtcMediaController::~WebRtcMediaController()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void WebRtcMediaController::report(Status status)
|
||||
{
|
||||
status.epoch = m_epoch.load();
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: report kind=" << static_cast<int>(status.kind)
|
||||
<< " code=" << static_cast<int>(status.code) << " epoch=" << status.epoch;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (status.kind == Status::Connecting)
|
||||
m_state = static_cast<wxMediaState>(4);
|
||||
else if (status.kind == Status::Playing)
|
||||
m_state = wxMEDIASTATE_PLAYING;
|
||||
else
|
||||
m_state = static_cast<wxMediaState>(3);
|
||||
}
|
||||
if (m_on_status)
|
||||
m_on_status(status);
|
||||
}
|
||||
|
||||
void WebRtcMediaController::set_signalling_channel(std::unique_ptr<ICameraSignalingChannel> channel)
|
||||
{
|
||||
m_pending_signaling = std::move(channel);
|
||||
}
|
||||
|
||||
void WebRtcMediaController::Play()
|
||||
{
|
||||
std::unique_ptr<ICameraSignalingChannel> channel = std::move(m_pending_signaling);
|
||||
// Tear down any previous attempt WITHOUT notifying: the Stopped that would
|
||||
// otherwise be delivered (async, via CallAfter) races the new attempt's
|
||||
// Connecting and makes the consumer cancel a session that is mid-connect.
|
||||
teardown(false);
|
||||
if (!channel)
|
||||
return;
|
||||
|
||||
m_epoch.fetch_add(1);
|
||||
m_alive.store(true);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_signaling = std::move(channel);
|
||||
m_jpeg_queue.clear();
|
||||
m_pending_candidates.clear();
|
||||
m_remote_description_set = false;
|
||||
m_video_size = wxDefaultSize;
|
||||
m_has_frame = false;
|
||||
m_last_frame_time = {};
|
||||
}
|
||||
|
||||
ICameraSignalingChannel* signaling = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
signaling = m_signaling.get();
|
||||
}
|
||||
signaling->on_ready = [this](std::vector<CameraIceServer> servers) {
|
||||
if (m_alive.load())
|
||||
on_ready(std::move(servers));
|
||||
};
|
||||
signaling->on_answer = [this](std::string sdp) {
|
||||
if (m_alive.load())
|
||||
on_answer(std::move(sdp));
|
||||
};
|
||||
signaling->on_ice = [this](std::string candidate, std::string mid) {
|
||||
if (m_alive.load())
|
||||
on_ice(std::move(candidate), std::move(mid));
|
||||
};
|
||||
signaling->on_unavailable = [this](CameraUnavailableReason reason, std::string detail) {
|
||||
if (m_alive.load())
|
||||
on_unavailable(reason, std::move(detail));
|
||||
};
|
||||
|
||||
m_decode_thread = std::thread([this] { decode_loop(); });
|
||||
report({Status::Connecting});
|
||||
signaling->open();
|
||||
}
|
||||
|
||||
void WebRtcMediaController::Stop()
|
||||
{
|
||||
teardown(true);
|
||||
}
|
||||
|
||||
void WebRtcMediaController::teardown(bool notify)
|
||||
{
|
||||
const bool was_alive = m_alive.exchange(false);
|
||||
if (!was_alive && !m_decode_thread.joinable())
|
||||
return;
|
||||
|
||||
m_cond.notify_all();
|
||||
std::unique_ptr<ICameraSignalingChannel> signaling;
|
||||
std::shared_ptr<rtc::PeerConnection> peer_connection;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
signaling = std::move(m_signaling);
|
||||
peer_connection = std::move(m_peer_connection);
|
||||
m_data_channel.reset();
|
||||
}
|
||||
if (signaling)
|
||||
signaling->close();
|
||||
if (peer_connection)
|
||||
peer_connection->close();
|
||||
if (m_decode_thread.joinable())
|
||||
m_decode_thread.join();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_jpeg_queue.clear();
|
||||
}
|
||||
if (was_alive && notify)
|
||||
report({Status::Stopped});
|
||||
}
|
||||
|
||||
wxMediaState WebRtcMediaController::GetState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_state;
|
||||
}
|
||||
|
||||
wxSize WebRtcMediaController::GetVideoSize() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_video_size;
|
||||
}
|
||||
|
||||
void WebRtcMediaController::bind_data_channel(const std::shared_ptr<rtc::DataChannel>& dc)
|
||||
{
|
||||
const std::string label = dc->label();
|
||||
dc->onOpen([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' open"; });
|
||||
dc->onClosed([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' closed"; });
|
||||
dc->onError([label](std::string e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC: data channel '" << label << "' error: " << e;
|
||||
});
|
||||
dc->onMessage(
|
||||
[this](rtc::binary data) {
|
||||
if (m_alive.load())
|
||||
enqueue_jpeg(std::vector<std::byte>(data.begin(), data.end()));
|
||||
},
|
||||
[](rtc::string) {});
|
||||
}
|
||||
|
||||
void WebRtcMediaController::on_ready(std::vector<CameraIceServer> servers)
|
||||
{
|
||||
init_rtc_logger_once();
|
||||
rtc::Configuration configuration;
|
||||
// Allow complete-JPEG DataChannel messages up to 1 MiB. This value is
|
||||
// advertised in SDP and becomes the upper bound for frames OrcaSonar can
|
||||
// send to OrcaSlicer.
|
||||
configuration.maxMessageSize = 1024 * 1024;
|
||||
for (const CameraIceServer& server : servers) {
|
||||
try {
|
||||
rtc::IceServer ice_server(server.urls);
|
||||
ice_server.username = server.username;
|
||||
ice_server.password = server.credential;
|
||||
configuration.iceServers.emplace_back(std::move(ice_server));
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC: invalid ICE server: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: creating peer connection with " << configuration.iceServers.size() << " ice servers";
|
||||
auto peer_connection = std::make_shared<rtc::PeerConnection>(std::move(configuration));
|
||||
peer_connection->onLocalDescription([this](rtc::Description description) {
|
||||
if (!m_alive.load())
|
||||
return;
|
||||
const std::string sdp(description);
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: local description ready (" << description.typeString()
|
||||
<< "), OFFER SDP:\n" << sdp;
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_signaling)
|
||||
m_signaling->send_offer(sdp);
|
||||
});
|
||||
peer_connection->onLocalCandidate([this](rtc::Candidate candidate) {
|
||||
if (!m_alive.load())
|
||||
return;
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_signaling)
|
||||
m_signaling->send_ice(std::string(candidate), candidate.mid());
|
||||
});
|
||||
peer_connection->onStateChange([this](rtc::PeerConnection::State state) {
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: peer state -> " << static_cast<int>(state);
|
||||
if (!m_alive.load())
|
||||
return;
|
||||
if (state == rtc::PeerConnection::State::Failed || state == rtc::PeerConnection::State::Disconnected)
|
||||
report({Status::Failed, Status::ICE_FAILED});
|
||||
});
|
||||
peer_connection->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: gathering state -> " << static_cast<int>(state);
|
||||
});
|
||||
|
||||
// Accept a DataChannel opened by the remote peer (OrcaSonar may create the
|
||||
// "camera" channel from its side rather than answering the one we offer).
|
||||
peer_connection->onDataChannel([this](std::shared_ptr<rtc::DataChannel> dc) {
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: remote opened data channel '" << dc->label() << "'";
|
||||
bind_data_channel(dc);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_data_channel = std::move(dc);
|
||||
});
|
||||
|
||||
rtc::DataChannelInit init;
|
||||
init.reliability.unordered = true;
|
||||
// init.reliability.maxPacketLifeTime = std::chrono::milliseconds(350);
|
||||
// Request one complete JPEG frame per DataChannel message. OrcaSonar
|
||||
// keeps the legacy chunked protocol for clients that omit this property.
|
||||
init.protocol = "orca-jpeg";
|
||||
auto data_channel = peer_connection->createDataChannel("camera", init);
|
||||
if (data_channel)
|
||||
bind_data_channel(data_channel);
|
||||
|
||||
// Camera media is carried as one complete JPEG per DataChannel message;
|
||||
// no RTP video track or application-level framing is required.
|
||||
|
||||
std::shared_ptr<rtc::PeerConnection> peer_for_description;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_alive.load())
|
||||
return;
|
||||
m_peer_connection = std::move(peer_connection);
|
||||
peer_for_description = m_peer_connection;
|
||||
m_data_channel = std::move(data_channel);
|
||||
}
|
||||
if (peer_for_description)
|
||||
peer_for_description->setLocalDescription();
|
||||
}
|
||||
|
||||
void WebRtcMediaController::on_answer(std::string sdp)
|
||||
{
|
||||
std::shared_ptr<rtc::PeerConnection> peer_connection;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
peer_connection = m_peer_connection;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: applying remote answer (" << sdp.size() << " bytes), ANSWER SDP:\n" << sdp;
|
||||
if (!peer_connection)
|
||||
return;
|
||||
try {
|
||||
peer_connection->setRemoteDescription(rtc::Description(sdp, "answer"));
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC: setRemoteDescription failed: " << e.what();
|
||||
report({Status::Failed, Status::ICE_FAILED});
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush any remote candidates that arrived before the answer.
|
||||
std::vector<std::pair<std::string, std::string>> pending;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_remote_description_set = true;
|
||||
pending.swap(m_pending_candidates);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: remote description set, flushing " << pending.size()
|
||||
<< " buffered candidate(s)";
|
||||
for (const auto& c : pending) {
|
||||
try {
|
||||
peer_connection->addRemoteCandidate(rtc::Candidate(c.first, c.second));
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate (buffered) failed: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcMediaController::on_ice(std::string candidate, std::string mid)
|
||||
{
|
||||
std::shared_ptr<rtc::PeerConnection> peer_connection;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_remote_description_set) {
|
||||
m_pending_candidates.emplace_back(std::move(candidate), std::move(mid));
|
||||
return;
|
||||
}
|
||||
peer_connection = m_peer_connection;
|
||||
}
|
||||
if (!peer_connection)
|
||||
return;
|
||||
try {
|
||||
peer_connection->addRemoteCandidate(rtc::Candidate(candidate, mid));
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate failed: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcMediaController::on_unavailable(CameraUnavailableReason reason, std::string detail)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "WebRTC camera unavailable: " << detail;
|
||||
Status::Code code = Status::UNAVAILABLE_ERROR;
|
||||
if (reason == CameraUnavailableReason::Busy)
|
||||
code = Status::UNAVAILABLE_BUSY;
|
||||
else if (reason == CameraUnavailableReason::Disabled)
|
||||
code = Status::UNAVAILABLE_DISABLED;
|
||||
else if (reason == CameraUnavailableReason::Closed)
|
||||
code = Status::SIGNALING_CLOSED;
|
||||
report({Status::Failed, code});
|
||||
}
|
||||
|
||||
void WebRtcMediaController::enqueue_jpeg(std::vector<std::byte> jpeg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_jpeg_queue.size() >= 4)
|
||||
m_jpeg_queue.pop_front();
|
||||
m_jpeg_queue.emplace_back(std::move(jpeg));
|
||||
m_cond.notify_one();
|
||||
}
|
||||
|
||||
void WebRtcMediaController::deliver_jpeg(std::vector<std::byte> jpeg)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_last_frame_time != std::chrono::steady_clock::time_point{} &&
|
||||
now - m_last_frame_time < std::chrono::milliseconds(33))
|
||||
return;
|
||||
m_last_frame_time = now;
|
||||
}
|
||||
wxMemoryInputStream stream(jpeg.data(), jpeg.size());
|
||||
wxImage image;
|
||||
if (!image.LoadFile(stream, wxBITMAP_TYPE_JPEG)) {
|
||||
report({Status::Failed, Status::DECODE_ERROR});
|
||||
return;
|
||||
}
|
||||
bool first_frame = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_video_size = image.GetSize();
|
||||
first_frame = !m_has_frame;
|
||||
m_has_frame = true;
|
||||
}
|
||||
if (m_frame_sink)
|
||||
m_frame_sink(image, image.GetSize());
|
||||
if (first_frame)
|
||||
report({Status::Playing});
|
||||
}
|
||||
|
||||
void WebRtcMediaController::decode_loop()
|
||||
{
|
||||
int stall_polls = 0;
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
while (m_alive.load()) {
|
||||
const bool woke = m_cond.wait_for(lock, std::chrono::seconds(2), [this] {
|
||||
return !m_alive.load() || !m_jpeg_queue.empty();
|
||||
});
|
||||
if (!m_alive.load())
|
||||
break;
|
||||
if (!woke && !m_has_frame) {
|
||||
const int pc_state = m_peer_connection ? static_cast<int>(m_peer_connection->state()) : -1;
|
||||
std::string dc = "none";
|
||||
if (m_data_channel)
|
||||
dc = "label='" + m_data_channel->label() + "' open=" +
|
||||
(m_data_channel->isOpen() ? "1" : "0");
|
||||
lock.unlock();
|
||||
BOOST_LOG_TRIVIAL(info) << "WebRTC: waiting for frames; peer_state=" << pc_state
|
||||
<< " data_channel=" << dc;
|
||||
if (++stall_polls >= 8) { // ~16s connected with no frame -> give up so the UI can retry
|
||||
report({Status::Failed, Status::TIMEOUT});
|
||||
lock.lock();
|
||||
break;
|
||||
}
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
stall_polls = 0;
|
||||
if (!m_jpeg_queue.empty()) {
|
||||
auto jpeg = std::move(m_jpeg_queue.front());
|
||||
m_jpeg_queue.pop_front();
|
||||
lock.unlock();
|
||||
deliver_jpeg(std::move(jpeg));
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "IMediaController.hpp"
|
||||
#include <slic3r/Utils/ICameraSignalingChannel.hpp>
|
||||
|
||||
#include <wx/image.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace rtc {
|
||||
class DataChannel;
|
||||
class PeerConnection;
|
||||
}
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class WebRtcMediaController : public IMediaController {
|
||||
public:
|
||||
struct Status {
|
||||
enum Kind { Connecting, Playing, Stopped, Failed } kind = Stopped;
|
||||
enum Code {
|
||||
ICE_FAILED,
|
||||
SIGNALING_CLOSED,
|
||||
UNAVAILABLE_BUSY,
|
||||
UNAVAILABLE_ERROR,
|
||||
UNAVAILABLE_DISABLED,
|
||||
DECODE_ERROR,
|
||||
TIMEOUT,
|
||||
} code = ICE_FAILED;
|
||||
// Identifies the Play attempt this status belongs to, so the
|
||||
// consumer can drop CallAfter-queued events from a superseded attempt.
|
||||
std::uint64_t epoch = 0;
|
||||
};
|
||||
|
||||
WebRtcMediaController(std::function<void(const wxImage&, wxSize)> frame_sink,
|
||||
std::function<void(Status)> on_status);
|
||||
~WebRtcMediaController() override;
|
||||
|
||||
void set_signalling_channel(std::unique_ptr<ICameraSignalingChannel> channel);
|
||||
std::uint64_t epoch() const { return m_epoch.load(); }
|
||||
bool is_active() const { return m_alive.load(); }
|
||||
|
||||
void Load(wxURI) override {}
|
||||
void Play() override;
|
||||
void Stop() override;
|
||||
wxMediaState GetState() override;
|
||||
wxSize GetVideoSize() const override;
|
||||
|
||||
private:
|
||||
void teardown(bool notify);
|
||||
void report(Status status);
|
||||
void bind_data_channel(const std::shared_ptr<rtc::DataChannel>& dc);
|
||||
void on_ready(std::vector<CameraIceServer> servers);
|
||||
void on_answer(std::string sdp);
|
||||
void on_ice(std::string candidate, std::string mid);
|
||||
void on_unavailable(CameraUnavailableReason reason, std::string detail);
|
||||
void decode_loop();
|
||||
void enqueue_jpeg(std::vector<std::byte> jpeg);
|
||||
void deliver_jpeg(std::vector<std::byte> jpeg);
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cond;
|
||||
std::deque<std::vector<std::byte>> m_jpeg_queue;
|
||||
// Remote candidates can arrive before the answer; libdatachannel rejects
|
||||
// addRemoteCandidate until a remote description is set, so buffer them.
|
||||
std::vector<std::pair<std::string, std::string>> m_pending_candidates;
|
||||
bool m_remote_description_set = false;
|
||||
std::unique_ptr<ICameraSignalingChannel> m_pending_signaling;
|
||||
std::unique_ptr<ICameraSignalingChannel> m_signaling;
|
||||
std::shared_ptr<rtc::PeerConnection> m_peer_connection;
|
||||
std::shared_ptr<rtc::DataChannel> m_data_channel;
|
||||
std::thread m_decode_thread;
|
||||
std::atomic<bool> m_alive{false};
|
||||
std::atomic<std::uint64_t> m_epoch{0};
|
||||
wxMediaState m_state = static_cast<wxMediaState>(3);
|
||||
wxSize m_video_size = wxDefaultSize;
|
||||
std::function<void(const wxImage&, wxSize)> m_frame_sink;
|
||||
std::function<void(Status)> m_on_status;
|
||||
bool m_has_frame = false;
|
||||
std::chrono::steady_clock::time_point m_last_frame_time{};
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
+366
-84
@@ -4,6 +4,14 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/dcclient.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/log.h>
|
||||
}
|
||||
#ifdef __WIN32__
|
||||
#include <versionhelpers.h>
|
||||
#include <wx/msw/registry.h>
|
||||
@@ -43,9 +51,13 @@ wxMediaCtrl3::~wxMediaCtrl3()
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
static void adjust_frame_size(wxSize& frame, wxSize const& video, wxSize const& window);
|
||||
|
||||
void wxMediaCtrl3::Load(wxURI url)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (m_external)
|
||||
return;
|
||||
m_video_size = wxDefaultSize;
|
||||
m_error = 0;
|
||||
m_url.reset(new wxURI(url));
|
||||
@@ -55,6 +67,8 @@ void wxMediaCtrl3::Load(wxURI url)
|
||||
void wxMediaCtrl3::Play()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (m_external)
|
||||
return;
|
||||
if (m_state != wxMEDIASTATE_PLAYING) {
|
||||
m_state = wxMEDIASTATE_PLAYING;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
@@ -74,6 +88,62 @@ void wxMediaCtrl3::Stop()
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::SetExternalFrame(const wxImage& frame, wxSize videoSize)
|
||||
{
|
||||
if (!frame.IsOk())
|
||||
return;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (!m_external)
|
||||
return;
|
||||
m_frame = frame;
|
||||
m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize();
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
}
|
||||
CallAfter([this] { Refresh(); });
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void wxMediaCtrl3::SetExternalFrame(const wxBitmap& frame, wxSize videoSize)
|
||||
{
|
||||
if (!frame.IsOk())
|
||||
return;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (!m_external)
|
||||
return;
|
||||
m_frame = frame;
|
||||
m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize();
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
}
|
||||
CallAfter([this] { Refresh(); });
|
||||
}
|
||||
#endif
|
||||
|
||||
void wxMediaCtrl3::BeginExternalStream()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_external = true;
|
||||
m_url.reset();
|
||||
m_active_url.reset();
|
||||
m_video_size = wxDefaultSize;
|
||||
m_frame = wxImage(m_idle_image);
|
||||
m_cond.notify_all();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::EndExternalStream()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_external = false;
|
||||
m_url.reset();
|
||||
m_active_url.reset();
|
||||
m_video_size = wxDefaultSize;
|
||||
m_frame = wxImage(m_idle_image);
|
||||
m_cond.notify_all();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::SetIdleImage(wxString const &image)
|
||||
{
|
||||
if (m_idle_image == image)
|
||||
@@ -93,13 +163,13 @@ wxMediaState wxMediaCtrl3::GetState()
|
||||
return m_state;
|
||||
}
|
||||
|
||||
int wxMediaCtrl3::GetLastError()
|
||||
int wxMediaCtrl3::GetLastError() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_error;
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl3::GetVideoSize()
|
||||
wxSize wxMediaCtrl3::GetVideoSize() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_video_size;
|
||||
@@ -185,6 +255,210 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
|
||||
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
|
||||
}
|
||||
|
||||
// FFmpeg's own diagnostics (HTTP status, "Invalid data found", demuxer choice,
|
||||
// missing stream dimensions, ...) are otherwise swallowed: a failed camera open
|
||||
// only surfaces as wxMediaCtrl3's generic error code, which MediaPlayCtrl maps to
|
||||
// the misleading "Player is malfunctioning" string. Forward them to the Orca log
|
||||
// instead. Verbosity defaults to AV_LOG_VERBOSE and can be raised at runtime with
|
||||
// ORCA_FFMPEG_LOG_LEVEL=debug|trace|... (or lowered to warning/error/quiet).
|
||||
static int ffmpeg_log_level_from_env()
|
||||
{
|
||||
const char *env = std::getenv("ORCA_FFMPEG_LOG_LEVEL");
|
||||
if (env == nullptr || *env == '\0')
|
||||
return AV_LOG_VERBOSE;
|
||||
const wxString v = wxString(env).Lower();
|
||||
if (v == "quiet") return AV_LOG_QUIET;
|
||||
if (v == "panic") return AV_LOG_PANIC;
|
||||
if (v == "fatal") return AV_LOG_FATAL;
|
||||
if (v == "error") return AV_LOG_ERROR;
|
||||
if (v == "warning") return AV_LOG_WARNING;
|
||||
if (v == "info") return AV_LOG_INFO;
|
||||
if (v == "verbose") return AV_LOG_VERBOSE;
|
||||
if (v == "debug") return AV_LOG_DEBUG;
|
||||
if (v == "trace") return AV_LOG_TRACE;
|
||||
return AV_LOG_VERBOSE;
|
||||
}
|
||||
|
||||
static void ffmpeg_log_callback(void *avcl, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
if (level > av_log_get_level())
|
||||
return;
|
||||
thread_local int print_prefix = 1;
|
||||
char line[1024];
|
||||
av_log_format_line2(avcl, level, fmt, vl, line, (int) sizeof(line), &print_prefix);
|
||||
size_t len = std::strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r' || line[len - 1] == ' '))
|
||||
line[--len] = '\0';
|
||||
if (len == 0)
|
||||
return;
|
||||
if (level <= AV_LOG_ERROR)
|
||||
BOOST_LOG_TRIVIAL(error) << "ffmpeg: " << line;
|
||||
else if (level <= AV_LOG_WARNING)
|
||||
BOOST_LOG_TRIVIAL(warning) << "ffmpeg: " << line;
|
||||
else if (level <= AV_LOG_INFO)
|
||||
BOOST_LOG_TRIVIAL(info) << "ffmpeg: " << line;
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(debug) << "ffmpeg: " << line;
|
||||
}
|
||||
|
||||
static void install_ffmpeg_logger()
|
||||
{
|
||||
av_log_set_level(ffmpeg_log_level_from_env());
|
||||
av_log_set_callback(&ffmpeg_log_callback);
|
||||
}
|
||||
|
||||
int wxMediaCtrl3::ffmpeg_interrupt_callback(void *opaque)
|
||||
{
|
||||
auto *ctrl = static_cast<wxMediaCtrl3 *>(opaque);
|
||||
std::lock_guard<std::mutex> lock(ctrl->m_mutex);
|
||||
return ctrl->m_url != ctrl->m_active_url;
|
||||
}
|
||||
|
||||
int wxMediaCtrl3::PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock)
|
||||
{
|
||||
static std::once_flag logger_once;
|
||||
std::call_once(logger_once, install_ffmpeg_logger);
|
||||
|
||||
if (avformat_network_init() < 0)
|
||||
return 2;
|
||||
|
||||
AVFormatContext *format_context = avformat_alloc_context();
|
||||
if (!format_context) {
|
||||
avformat_network_deinit();
|
||||
return 2;
|
||||
}
|
||||
|
||||
format_context->interrupt_callback = {&wxMediaCtrl3::ffmpeg_interrupt_callback, this};
|
||||
format_context->flags |= AVFMT_FLAG_NOBUFFER;
|
||||
format_context->max_delay = 0;
|
||||
m_active_url = url;
|
||||
|
||||
auto finish = [&](int error) {
|
||||
lock.unlock();
|
||||
avformat_close_input(&format_context);
|
||||
avformat_network_deinit();
|
||||
lock.lock();
|
||||
m_active_url.reset();
|
||||
return error;
|
||||
};
|
||||
|
||||
const std::string uri = url->BuildURI().ToUTF8().data();
|
||||
const wxString scheme = url->GetScheme();
|
||||
const bool http_stream = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0;
|
||||
AVDictionary *options = nullptr;
|
||||
if (http_stream) {
|
||||
// Live multipart MJPEG. fflags=nobuffer / AVFMT_FLAG_NOBUFFER / max_delay=0
|
||||
// (set above) are the low-latency levers - they disable the demuxer
|
||||
// read-ahead queue. probesize / analyzeduration only bound the one-off
|
||||
// avformat_find_stream_info() at open; a 32-byte budget returned before a
|
||||
// whole JPEG frame was seen, so width/height came back unset and the open
|
||||
// was rejected. Give it room to identify one frame (a startup cost only).
|
||||
// rw_timeout / timeout bound a wedged connect or read so a stale stream
|
||||
// fails fast and is retried, instead of the reader thread hanging.
|
||||
// avioflags=direct is deliberately NOT set: unbuffered reads make the
|
||||
// mpjpeg demuxer emit "Packet corrupt" and bail on any short read across
|
||||
// a multipart boundary.
|
||||
av_dict_set(&options, "fflags", "nobuffer", 0);
|
||||
av_dict_set(&options, "probesize", "5000000", 0);
|
||||
av_dict_set(&options, "analyzeduration", "1000000", 0);
|
||||
av_dict_set(&options, "rw_timeout", "5000000", 0);
|
||||
av_dict_set(&options, "timeout", "5000000", 0);
|
||||
} else {
|
||||
av_dict_set(&options, "rtsp_transport", "tcp", 0);
|
||||
}
|
||||
lock.unlock();
|
||||
int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options);
|
||||
av_dict_free(&options);
|
||||
lock.lock();
|
||||
if (error < 0)
|
||||
return finish(2);
|
||||
|
||||
lock.unlock();
|
||||
error = avformat_find_stream_info(format_context, nullptr);
|
||||
lock.lock();
|
||||
if (error < 0)
|
||||
return finish(2);
|
||||
|
||||
const int video_stream = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
||||
if (video_stream < 0)
|
||||
return finish(2);
|
||||
|
||||
AVVideoDecoder decoder;
|
||||
if (decoder.open(*format_context->streams[video_stream]->codecpar) < 0)
|
||||
return finish(2);
|
||||
|
||||
// Prefer the dimensions the container reported. A small probe budget, or a
|
||||
// camera that doesn't announce a size up front, can leave these unset - in
|
||||
// that case fill them in from the first frame that decodes (below) rather
|
||||
// than failing the open outright.
|
||||
auto apply_video_size = [&](wxSize size) {
|
||||
if (!size.IsFullySpecified() || size.x <= 0 || size.y <= 0)
|
||||
return false;
|
||||
m_video_size = size;
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
NotifyStopped();
|
||||
return true;
|
||||
};
|
||||
bool have_size = apply_video_size({format_context->streams[video_stream]->codecpar->width,
|
||||
format_context->streams[video_stream]->codecpar->height});
|
||||
int size_probe_frames = 0; // frames spent still waiting for a usable size
|
||||
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
if (!packet)
|
||||
return finish(2);
|
||||
|
||||
while (m_url == url) {
|
||||
lock.unlock();
|
||||
error = av_read_frame(format_context, packet);
|
||||
lock.lock();
|
||||
if (m_url != url)
|
||||
break;
|
||||
if (error < 0)
|
||||
break;
|
||||
if (packet->stream_index == video_stream) {
|
||||
const int decode_error = decoder.decode(*packet);
|
||||
if (decode_error == 0) {
|
||||
if (!have_size) {
|
||||
have_size = apply_video_size(decoder.decoded_frame_size());
|
||||
if (!have_size) {
|
||||
av_packet_unref(packet);
|
||||
// MJPEG yields a sized frame on the first full packet; if
|
||||
// several seconds of frames never do, treat it as a bad
|
||||
// stream instead of sitting in "Loading..." forever.
|
||||
if (++size_probe_frames > 120)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto frame_size = m_frame_size;
|
||||
lock.unlock();
|
||||
#ifdef _WIN32
|
||||
wxBitmap frame;
|
||||
decoder.toWxBitmap(frame, frame_size);
|
||||
#else
|
||||
wxImage frame;
|
||||
decoder.toWxImage(frame, frame_size);
|
||||
#endif
|
||||
lock.lock();
|
||||
if (m_url != url)
|
||||
break;
|
||||
if (frame.IsOk())
|
||||
m_frame = frame;
|
||||
if (!m_refresh_pending.exchange(true)) {
|
||||
CallAfter([this] {
|
||||
m_refresh_pending.store(false);
|
||||
Refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
return finish(m_url == url ? 2 : 1);
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::PlayThread()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
@@ -197,42 +471,22 @@ void wxMediaCtrl3::PlayThread()
|
||||
continue;
|
||||
if (!url->HasScheme())
|
||||
break;
|
||||
lk.unlock();
|
||||
Bambu_Tunnel tunnel = nullptr;
|
||||
int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
|
||||
if (error == 0) {
|
||||
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
|
||||
error = Bambu_Open(tunnel);
|
||||
if (error == 0)
|
||||
error = Bambu_would_block;
|
||||
}
|
||||
lk.lock();
|
||||
while (error == int(Bambu_would_block)) {
|
||||
m_cond.wait_for(lk, 100ms);
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
const wxString scheme = url->GetScheme();
|
||||
const bool generic_ffmpeg = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0 ||
|
||||
scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0;
|
||||
int error = 0;
|
||||
if (generic_ffmpeg) {
|
||||
error = PlayFfmpeg(url, lk);
|
||||
} else {
|
||||
lk.unlock();
|
||||
Bambu_Tunnel tunnel = nullptr;
|
||||
error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
|
||||
if (error == 0) {
|
||||
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
|
||||
error = Bambu_Open(tunnel);
|
||||
if (error == 0)
|
||||
error = Bambu_would_block;
|
||||
}
|
||||
lk.unlock();
|
||||
error = Bambu_StartStream(tunnel, true);
|
||||
lk.lock();
|
||||
}
|
||||
Bambu_StreamInfo info;
|
||||
if (error == 0)
|
||||
error = Bambu_GetStreamInfo(tunnel, 0, &info);
|
||||
AVVideoDecoder decoder;
|
||||
int minFrameDuration = 0;
|
||||
if (error == 0) {
|
||||
decoder.open(info);
|
||||
m_video_size = { info.format.video.width, info.format.video.height };
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
|
||||
NotifyStopped();
|
||||
}
|
||||
Bambu_Sample sample;
|
||||
while (error == 0) {
|
||||
lk.unlock();
|
||||
error = Bambu_ReadSample(tunnel, &sample);
|
||||
lk.lock();
|
||||
while (error == int(Bambu_would_block)) {
|
||||
m_cond.wait_for(lk, 100ms);
|
||||
@@ -240,59 +494,87 @@ void wxMediaCtrl3::PlayThread()
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
lk.unlock();
|
||||
error = Bambu_StartStream(tunnel, true);
|
||||
lk.lock();
|
||||
}
|
||||
Bambu_StreamInfo info;
|
||||
if (error == 0)
|
||||
error = Bambu_GetStreamInfo(tunnel, 0, &info);
|
||||
AVVideoDecoder decoder;
|
||||
int minFrameDuration = 0;
|
||||
if (error == 0) {
|
||||
decoder.open(info);
|
||||
m_video_size = { info.format.video.width, info.format.video.height };
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
|
||||
NotifyStopped();
|
||||
}
|
||||
Bambu_Sample sample;
|
||||
while (error == 0) {
|
||||
lk.unlock();
|
||||
error = Bambu_ReadSample(tunnel, &sample);
|
||||
lk.lock();
|
||||
}
|
||||
if (error == 0) {
|
||||
auto frame_size = m_frame_size;
|
||||
lk.unlock();
|
||||
decoder.decode(sample);
|
||||
#ifdef _WIN32
|
||||
wxBitmap bm;
|
||||
decoder.toWxBitmap(bm, frame_size);
|
||||
#else
|
||||
wxImage bm;
|
||||
decoder.toWxImage(bm, frame_size);
|
||||
#endif
|
||||
lk.lock();
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
if (bm.IsOk()) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
|
||||
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
|
||||
// The frame is late, catch up a little
|
||||
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
|
||||
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
|
||||
if(now < next_PTS)
|
||||
std::this_thread::sleep_until(next_PTS);
|
||||
else
|
||||
next_PTS = now;
|
||||
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
|
||||
//OutputDebugString(text);
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = next_PTS_expected;
|
||||
m_last_PTS_practical = next_PTS;
|
||||
} else {
|
||||
// Resync
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = now;
|
||||
m_last_PTS_practical = now;
|
||||
while (error == int(Bambu_would_block)) {
|
||||
m_cond.wait_for(lk, 100ms);
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
m_frame = bm;
|
||||
lk.unlock();
|
||||
error = Bambu_ReadSample(tunnel, &sample);
|
||||
lk.lock();
|
||||
}
|
||||
if (error == 0) {
|
||||
auto frame_size = m_frame_size;
|
||||
lk.unlock();
|
||||
decoder.decode(sample);
|
||||
#ifdef _WIN32
|
||||
wxBitmap bm;
|
||||
decoder.toWxBitmap(bm, frame_size);
|
||||
#else
|
||||
wxImage bm;
|
||||
decoder.toWxImage(bm, frame_size);
|
||||
#endif
|
||||
lk.lock();
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
if (bm.IsOk()) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
|
||||
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
|
||||
// The frame is late, catch up a little
|
||||
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
|
||||
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
|
||||
if(now < next_PTS)
|
||||
std::this_thread::sleep_until(next_PTS);
|
||||
else
|
||||
next_PTS = now;
|
||||
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
|
||||
//OutputDebugString(text);
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = next_PTS_expected;
|
||||
m_last_PTS_practical = next_PTS;
|
||||
} else {
|
||||
// Resync
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = now;
|
||||
m_last_PTS_practical = now;
|
||||
}
|
||||
m_frame = bm;
|
||||
}
|
||||
CallAfter([this] { Refresh(); });
|
||||
}
|
||||
CallAfter([this] { Refresh(); });
|
||||
}
|
||||
}
|
||||
if (tunnel) {
|
||||
lk.unlock();
|
||||
Bambu_Close(tunnel);
|
||||
Bambu_Destroy(tunnel);
|
||||
tunnel = nullptr;
|
||||
lk.lock();
|
||||
if (tunnel) {
|
||||
lk.unlock();
|
||||
Bambu_Close(tunnel);
|
||||
Bambu_Destroy(tunnel);
|
||||
tunnel = nullptr;
|
||||
lk.lock();
|
||||
}
|
||||
}
|
||||
if (m_url == url)
|
||||
m_error = error;
|
||||
|
||||
@@ -13,41 +13,51 @@
|
||||
#include "wx/bitmap.h"
|
||||
#include "wx/uri.h"
|
||||
#include "wx/mediactrl.h"
|
||||
#include "IMediaController.hpp"
|
||||
|
||||
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
|
||||
|
||||
#define BAMBU_DYNAMIC
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#ifndef _WIN32
|
||||
#include <wx/image.h>
|
||||
#endif
|
||||
#include "Printer/BambuTunnel.h"
|
||||
|
||||
class AVVideoDecoder;
|
||||
|
||||
class wxMediaCtrl3 : public wxWindow, BambuLib
|
||||
class wxMediaCtrl3 : public wxWindow, public Slic3r::GUI::IMediaController, BambuLib
|
||||
{
|
||||
public:
|
||||
wxMediaCtrl3(wxWindow *parent);
|
||||
|
||||
~wxMediaCtrl3();
|
||||
~wxMediaCtrl3() override;
|
||||
|
||||
void Load(wxURI url);
|
||||
void Load(wxURI url) override;
|
||||
|
||||
void Play();
|
||||
void Play() override;
|
||||
|
||||
void Stop();
|
||||
void Stop() override;
|
||||
|
||||
// Render frames supplied by a controller which owns its own transport.
|
||||
// The frame is copied while m_mutex is held; callers may release it after
|
||||
// this method returns.
|
||||
void SetExternalFrame(const wxImage& frame, wxSize videoSize);
|
||||
#ifdef _WIN32
|
||||
void SetExternalFrame(const wxBitmap& frame, wxSize videoSize);
|
||||
#endif
|
||||
void BeginExternalStream();
|
||||
void EndExternalStream();
|
||||
|
||||
void SetIdleImage(wxString const & image);
|
||||
|
||||
wxMediaState GetState();
|
||||
wxMediaState GetState() override;
|
||||
|
||||
int GetLastError();
|
||||
int GetLastError() const override;
|
||||
|
||||
wxSize GetVideoSize();
|
||||
wxSize GetVideoSize() const override;
|
||||
|
||||
protected:
|
||||
DECLARE_EVENT_TABLE()
|
||||
@@ -59,8 +69,10 @@ protected:
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
|
||||
static void bambu_log(void *ctx, int level, tchar const *msg);
|
||||
static int ffmpeg_interrupt_callback(void *opaque);
|
||||
|
||||
void PlayThread();
|
||||
int PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock);
|
||||
|
||||
void NotifyStopped();
|
||||
|
||||
@@ -77,12 +89,15 @@ private:
|
||||
#endif
|
||||
|
||||
std::shared_ptr<wxURI> m_url;
|
||||
std::shared_ptr<wxURI> m_active_url;
|
||||
bool m_external = false;
|
||||
std::uint64_t m_last_PTS{0};
|
||||
std::chrono::system_clock::time_point m_last_PTS_expected;
|
||||
std::chrono::system_clock::time_point m_last_PTS_practical;
|
||||
std::mutex m_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cond;
|
||||
std::thread m_thread;
|
||||
std::atomic_bool m_refresh_pending{false};
|
||||
};
|
||||
|
||||
#endif /* wxMediaCtrl3_h */
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
#include "BBLPrinterAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevManager.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <cmath>
|
||||
#include <slic3r/GUI/DeviceManager.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -133,7 +141,7 @@ BBLPrinterAgent::~BBLPrinterAgent() = default;
|
||||
|
||||
void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
||||
{
|
||||
m_cloud_agent = cloud;
|
||||
(void) cloud;
|
||||
// BBL DLL manages tokens internally, so this is just for interface compliance
|
||||
}
|
||||
|
||||
@@ -141,6 +149,94 @@ void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
||||
// Communication
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
if (ams_id == -1) {
|
||||
const std::string gcode = (boost::format("M620 R%1% \n") % slot_id).str();
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = gcode;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
j["print"]["command"] = "ams_get_rfid";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["ams_id"] = ams_id;
|
||||
j["print"]["slot_id"] = slot_id;
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
const std::string gcode = (boost::format("M620 C%1% \n") % ams_id).str();
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = gcode;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
const std::string gcode = (boost::format("M620 P%1% \n") % tray_id).str();
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = gcode;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed,
|
||||
bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
|
||||
if (supports_mqtt_axis_control) {
|
||||
int dir = input_val > 0 ? 1 : -1;
|
||||
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
|
||||
// reversed -- same negation the g-code fallback below applies.
|
||||
if (!is_core_xy && (axis == "Y" || axis == "Z"))
|
||||
dir = -dir;
|
||||
|
||||
j["print"]["command"] = "xyz_ctrl";
|
||||
j["print"]["axis"] = axis;
|
||||
j["print"]["dir"] = dir;
|
||||
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
double value = input_val;
|
||||
if (!is_core_xy && (axis == "Y" || axis == "Z"))
|
||||
value = -input_val;
|
||||
|
||||
std::string value_str = (boost::format("%.1f") % (value * unit)).str();
|
||||
std::string gcode;
|
||||
if (axis == "X" || axis == "Y" || axis == "Z") {
|
||||
gcode = (boost::format("M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %1%%2% F%3%\nM1002 pop_ref_mode\nM211 R\n")
|
||||
% axis % value_str % speed).str();
|
||||
} else if (axis == "E") {
|
||||
gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = gcode;
|
||||
return publish(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode)
|
||||
{
|
||||
const int rtn = lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0) : send_message(dev_id, j.dump(), 0, 0);
|
||||
if (rtn == 0)
|
||||
BOOST_LOG_TRIVIAL(info) << "publish_json: " << j.dump() << " code: " << rtn;
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "publish_json: " << j.dump() << " code: " << rtn;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
json_str = from_orca_payload(std::move(json_str));
|
||||
@@ -165,13 +261,23 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
int BBLPrinterAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_connect_printer();
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
const bool use_ssl_for_mqtt = GUI::wxGetApp().app_config &&
|
||||
GUI::wxGetApp().app_config->get_bool("enable_ssl_for_mqtt");
|
||||
#else
|
||||
bool use_ssl_for_mqtt = true;
|
||||
if (auto* dev_manager = GUI::wxGetApp().getDeviceManager()) {
|
||||
if (auto* machine = dev_manager->get_my_machine(params.dev_id))
|
||||
use_ssl_for_mqtt = machine->local_use_ssl;
|
||||
}
|
||||
#endif
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, dev_ip, username, password, use_ssl);
|
||||
return func(agent, params.dev_id, params.host, params.username, params.password, use_ssl_for_mqtt);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -28,7 +29,13 @@ public:
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) override;
|
||||
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override;
|
||||
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override;
|
||||
int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed,
|
||||
bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode) override;
|
||||
std::string default_lan_username() const override { return "bblp"; }
|
||||
int connect_printer(const PrinterConnectionParams& params) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
@@ -99,7 +106,7 @@ public:
|
||||
static std::string from_orca_payload(std::string json_text);
|
||||
|
||||
private:
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -282,7 +282,7 @@ bool CrealityPrintAgent::parse_cfs_response(const std::string& response,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrealityPrintAgent::fetch_filament_info(std::string dev_id)
|
||||
bool CrealityPrintAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/)
|
||||
{
|
||||
if (device_info.dev_ip.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
||||
|
||||
// Parse the boxsInfo JSON returned by CrealityPrint::query_boxes_info() into
|
||||
// a flat list of loaded slots, plus the count of CFS boxes the printer reports.
|
||||
|
||||
@@ -224,4 +224,4 @@ void FileTransferJob::solve_result(ft_job_result result)
|
||||
res_json_.assign(result.json ? result.json : "");
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -252,4 +252,4 @@ inline FileTransferModule &module()
|
||||
return *detail::g_mod;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct CameraIceServer {
|
||||
std::string urls;
|
||||
std::string username;
|
||||
std::string credential;
|
||||
};
|
||||
|
||||
enum class CameraUnavailableReason {
|
||||
Busy,
|
||||
Error,
|
||||
Disabled,
|
||||
Closed,
|
||||
};
|
||||
|
||||
class ICameraSignalingChannel {
|
||||
public:
|
||||
virtual ~ICameraSignalingChannel() = default;
|
||||
|
||||
virtual void open() = 0;
|
||||
virtual void close() = 0;
|
||||
virtual void send_offer(std::string sdp) = 0;
|
||||
virtual void send_ice(std::string candidate, std::string mid) = 0;
|
||||
|
||||
// These callbacks are invoked by the channel's worker thread. Consumers
|
||||
// must marshal UI work to the GUI thread themselves.
|
||||
std::function<void(std::vector<CameraIceServer>)> on_ready;
|
||||
std::function<void(std::string)> on_answer;
|
||||
std::function<void(std::string, std::string)> on_ice;
|
||||
std::function<void(CameraUnavailableReason, std::string)> on_unavailable;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
#define __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "ICameraSignalingChannel.hpp"
|
||||
#include "bambu_networking.hpp"
|
||||
#include "CloudProvider.hpp"
|
||||
#include "../../libslic3r/ProjectTask.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
@@ -328,6 +328,17 @@ public:
|
||||
*/
|
||||
virtual int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Create a camera signaling channel object for P2P camera streaming over WebRTC.
|
||||
*
|
||||
*/
|
||||
virtual std::unique_ptr<ICameraSignalingChannel>
|
||||
create_camera_signaling_channel(const std::string& dev_id)
|
||||
{
|
||||
(void) dev_id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch staff-picked designs from model mall.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
#define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -29,6 +34,17 @@ struct AgentInfo {
|
||||
std::string description; ///< Brief description of the agent's capabilities, e.g. "Orca printer agent"
|
||||
};
|
||||
|
||||
struct PrinterConnectionParams
|
||||
{
|
||||
std::string dev_id;
|
||||
std::string host; // host address, usually the IP address without the http/https protocol
|
||||
std::string port; // optional
|
||||
std::string username;
|
||||
std::string password;
|
||||
bool use_ssl = false; // indicates if http or https
|
||||
std::string ca_file;
|
||||
};
|
||||
|
||||
/**
|
||||
* FilamentSyncMode - Modes for filament data synchronization.
|
||||
*
|
||||
@@ -43,6 +59,15 @@ enum class FilamentSyncMode {
|
||||
pull ///< On-demand fetch via REST API (blocking call)
|
||||
};
|
||||
|
||||
enum class CameraStreamMode {
|
||||
none = 0,
|
||||
http, // LAN or Cloud
|
||||
https, // LAN or Cloud over TLS
|
||||
rtsp, // LAN only
|
||||
webrtc, // Cloud only
|
||||
http_snapshot // HTTP endpoint returning one image per request
|
||||
};
|
||||
|
||||
/**
|
||||
* IPrinterAgent - Interface for printer operations.
|
||||
*
|
||||
@@ -84,10 +109,112 @@ public:
|
||||
*/
|
||||
virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0;
|
||||
|
||||
// why: gcode is firmware dialect, not a waist concept - commands whose body is Bambu-dialect
|
||||
// gcode live on the agent that speaks it; the default is an honest refusal that MachineObject's
|
||||
// publish funnel turns into a dialog.
|
||||
virtual int command_ams_refresh_rfid(std::string, int, int, int, bool)
|
||||
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
|
||||
virtual int command_ams_calibrate(std::string, int, int, bool)
|
||||
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
|
||||
virtual int command_ams_select_tray(std::string, std::string, int, bool)
|
||||
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
|
||||
virtual int command_start_camera(std::string)
|
||||
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
|
||||
|
||||
private:
|
||||
int publish_command_json(std::string dev_id, const nlohmann::json& j, bool lan_mode)
|
||||
{
|
||||
return lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0)
|
||||
: send_message(dev_id, j.dump(), 0, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = "G90 \n";
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
virtual int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = "G29 \n";
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
virtual int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
if (supports_mqtt_homing) {
|
||||
j["print"]["command"] = "back_to_center";
|
||||
} else {
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = is_printing ? "G28 X\n" : "G28 \n";
|
||||
}
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
virtual int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
if (supports_mqtt_bed_ctrl) {
|
||||
j["print"]["command"] = "set_bed_temp";
|
||||
j["print"]["temp"] = temp;
|
||||
} else {
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = (boost::format("M140 S%1%\n") % temp).str();
|
||||
}
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
virtual int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = (boost::format("M104 S%1%\n") % temp).str();
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
virtual int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed,
|
||||
bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode)
|
||||
{
|
||||
(void) supports_mqtt_axis_control;
|
||||
|
||||
double value = input_val;
|
||||
if (!is_core_xy && (axis == "Y" || axis == "Z"))
|
||||
value = -input_val;
|
||||
|
||||
std::string value_str = (boost::format("%.1f") % (value * unit)).str();
|
||||
std::string gcode;
|
||||
if (axis == "X" || axis == "Y" || axis == "Z") {
|
||||
gcode = (boost::format("G91 \nG1 %1%%2% F%3%\nG90 \n") % axis % value_str % speed).str();
|
||||
} else if (axis == "E") {
|
||||
gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_line";
|
||||
j["print"]["param"] = gcode;
|
||||
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||
return publish_command_json(dev_id, j, lan_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default LAN account username for this agent's protocol, if it has a fixed one.
|
||||
* Returns an empty string if the agent has no fixed default (e.g. caller must supply one).
|
||||
*/
|
||||
virtual std::string default_lan_username() const { return {}; }
|
||||
|
||||
/**
|
||||
* Establish a direct LAN connection to a printer.
|
||||
*/
|
||||
virtual int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) = 0;
|
||||
virtual int connect_printer(const PrinterConnectionParams& params) = 0;
|
||||
|
||||
/**
|
||||
* Tear down the active LAN printer connection.
|
||||
@@ -105,12 +232,16 @@ public:
|
||||
/**
|
||||
* Validate current user certificates for the printer.
|
||||
*/
|
||||
virtual int check_cert() = 0;
|
||||
virtual int check_cert() { return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
/**
|
||||
* Install or refresh device certificate for LAN TLS.
|
||||
*/
|
||||
virtual void install_device_cert(std::string dev_id, bool lan_only) = 0;
|
||||
virtual void install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
(void) dev_id;
|
||||
(void) lan_only;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Discovery
|
||||
@@ -126,7 +257,11 @@ public:
|
||||
/**
|
||||
* Ping the binding endpoint to check printer readiness.
|
||||
*/
|
||||
virtual int ping_bind(std::string ping_code) = 0;
|
||||
virtual int ping_bind(std::string ping_code)
|
||||
{
|
||||
(void) ping_code;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform binding detection/handshake on a LAN printer.
|
||||
@@ -136,23 +271,50 @@ public:
|
||||
/**
|
||||
* Execute the multi-stage printer binding workflow.
|
||||
*/
|
||||
virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) = 0;
|
||||
virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link,
|
||||
std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
(void) dev_ip;
|
||||
(void) dev_id;
|
||||
(void) dev_model;
|
||||
(void) sec_link;
|
||||
(void) timezone;
|
||||
(void) improved;
|
||||
(void) update_fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the association between account and printer.
|
||||
*/
|
||||
virtual int unbind(std::string dev_id) = 0;
|
||||
virtual int unbind(std::string dev_id)
|
||||
{
|
||||
(void) dev_id;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a one-time bind ticket from the server.
|
||||
*/
|
||||
virtual int request_bind_ticket(std::string* ticket) = 0;
|
||||
virtual int request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (ticket)
|
||||
*ticket = {};
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cloud snapshot image captured at a print failure.
|
||||
* Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status).
|
||||
*/
|
||||
virtual int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) = 0;
|
||||
virtual int get_hms_snapshot(std::string dev_id, std::string file_name,
|
||||
std::function<void(std::string, int)> callback)
|
||||
{
|
||||
(void) dev_id;
|
||||
(void) file_name;
|
||||
(void) callback;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback for fatal HTTP errors.
|
||||
@@ -168,7 +330,7 @@ public:
|
||||
virtual std::string get_user_selected_machine() = 0;
|
||||
|
||||
/**
|
||||
* Update the selected machine preference.
|
||||
* Update the selected cloud machine preference.
|
||||
*/
|
||||
virtual int set_user_selected_machine(std::string dev_id) = 0;
|
||||
|
||||
@@ -284,12 +446,20 @@ public:
|
||||
*/
|
||||
virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; }
|
||||
|
||||
/**
|
||||
* Get the camera stream mode for this agent. This value can be deterministic and derived at
|
||||
* runtime if the printer supports multiple camera stream modes. E.g. LAN => HTTP/HTTPS/RTSP, Cloud => WebRTC.
|
||||
*
|
||||
* @return CameraStreamMode indicating how the camera stream is obtained or used:
|
||||
*/
|
||||
virtual CameraStreamMode get_camera_stream_mode() const { return CameraStreamMode::none; }
|
||||
|
||||
/**
|
||||
* Refresh filament info from the printer synchronously.
|
||||
* Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull.
|
||||
* Populates the MachineObject's DevFilaSystem with fetched filament data.
|
||||
*/
|
||||
virtual bool fetch_filament_info(std::string dev_id) { return false; }
|
||||
virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; }
|
||||
|
||||
/**
|
||||
* Translate one filament id across the printer boundary.
|
||||
@@ -300,6 +470,12 @@ public:
|
||||
*/
|
||||
virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; }
|
||||
virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; }
|
||||
|
||||
/**
|
||||
* Get the current camera stream URL for this agent's active machine.
|
||||
* Only meaningful when get_camera_stream_mode() returns an HTTP, HTTPS, or RTSP mode.
|
||||
*/
|
||||
virtual std::string get_camera_url() const { return {}; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "MoonrakerPrinterAgent.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
@@ -136,9 +137,9 @@ int MoonrakerPrinterAgent::send_message_to_printer(std::string dev_id, std::stri
|
||||
return handle_request(dev_id, json_str);
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
int MoonrakerPrinterAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
{
|
||||
if (dev_id.empty() || dev_ip.empty()) {
|
||||
if (params.dev_id.empty() || params.host.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip";
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -148,7 +149,7 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i
|
||||
uint64_t gen;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
|
||||
init_device_info(dev_id, dev_ip, username, password, use_ssl);
|
||||
init_device_info(params.dev_id, params.host, params.username, params.password, params.use_ssl, params.port);
|
||||
gen = ++connect_generation;
|
||||
base_url = device_info.base_url;
|
||||
api_key = device_info.api_key;
|
||||
@@ -170,7 +171,7 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i
|
||||
// Launch connection in background thread (capture by value to avoid data races)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
|
||||
connect_thread = std::thread([this, dev_id, base_url, api_key, gen]() { perform_connection_async(dev_id, base_url, api_key, gen); });
|
||||
connect_thread = std::thread([this, dev_id = params.dev_id, base_url, api_key, gen]() { perform_connection_async(dev_id, base_url, api_key, gen); });
|
||||
}
|
||||
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
@@ -191,14 +192,6 @@ int MoonrakerPrinterAgent::disconnect_printer()
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::check_cert() { return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
void MoonrakerPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
(void) dev_id;
|
||||
(void) lan_only;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::start_discovery(bool start, bool sending)
|
||||
{
|
||||
(void) sending;
|
||||
@@ -208,12 +201,6 @@ bool MoonrakerPrinterAgent::start_discovery(bool start, bool sending)
|
||||
return true;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
(void) ping_code;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
{
|
||||
(void) sec_link;
|
||||
@@ -221,7 +208,7 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
|
||||
detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id;
|
||||
detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id;
|
||||
// Prefer fetched hostname, then preset model name, then generic fallback
|
||||
detect.dev_name = device_info.dev_name;
|
||||
detect.dev_name = device_info.dev_name.empty() ? dev_ip : device_info.dev_name;
|
||||
detect.model_id = device_info.model_id;
|
||||
detect.version = device_info.version;
|
||||
detect.connect_type = "lan";
|
||||
@@ -230,41 +217,6 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::bind(
|
||||
std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
(void) dev_ip;
|
||||
(void) dev_id;
|
||||
(void) dev_model;
|
||||
(void) sec_link;
|
||||
(void) timezone;
|
||||
(void) improved;
|
||||
(void) update_fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::unbind(std::string dev_id)
|
||||
{
|
||||
(void) dev_id;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (ticket)
|
||||
*ticket = "";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
// No BBL cloud snapshot source; report failure so the caller falls back.
|
||||
(void) dev_id;
|
||||
(void) file_name;
|
||||
(void) callback;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(state_mutex);
|
||||
@@ -385,7 +337,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageSending, 0, "Starting print...");
|
||||
std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename;
|
||||
if (!send_gcode(device_info.dev_id, gcode)) {
|
||||
if (!send_gcode_sync(device_info.dev_id, gcode)) {
|
||||
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
|
||||
}
|
||||
|
||||
@@ -578,7 +530,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
}
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/)
|
||||
{
|
||||
std::vector<AmsTrayData> trays;
|
||||
int max_lane_index = 0;
|
||||
@@ -1112,14 +1064,11 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
||||
}
|
||||
response["print"]["param"] = gcode;
|
||||
|
||||
if (send_gcode(dev_id, gcode)) {
|
||||
response["print"]["result"] = "success";
|
||||
send_gcode_async(dev_id, gcode, [this, dev_id, response](bool success) mutable {
|
||||
response["print"]["result"] = success ? "success" : "failed";
|
||||
dispatch_message(dev_id, response.dump());
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
response["print"]["result"] = "failed";
|
||||
dispatch_message(dev_id, response.dump());
|
||||
return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
||||
});
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// Print control commands
|
||||
@@ -1138,7 +1087,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
||||
if (json["print"].contains("temp") && json["print"]["temp"].is_number()) {
|
||||
int temp = json["print"]["temp"].get<int>();
|
||||
std::string gcode = "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=" + std::to_string(temp);
|
||||
send_gcode(dev_id, gcode);
|
||||
send_gcode_async(dev_id, gcode);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -1153,20 +1102,21 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
||||
}
|
||||
std::string heater = (extruder_idx == 0) ? "extruder" : "extruder" + std::to_string(extruder_idx);
|
||||
std::string gcode = "SET_HEATER_TEMPERATURE HEATER=" + heater + " TARGET=" + std::to_string(temp);
|
||||
send_gcode(dev_id, gcode);
|
||||
send_gcode_async(dev_id, gcode);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd == "home") {
|
||||
return send_gcode(dev_id, "G28") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
send_gcode_async(dev_id, "G28");
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
bool MoonrakerPrinterAgent::init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port)
|
||||
{
|
||||
device_info = MoonrakerDeviceInfo{};
|
||||
auto* preset_bundle = GUI::wxGetApp().preset_bundle;
|
||||
@@ -1176,12 +1126,12 @@ bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev
|
||||
|
||||
auto& preset = preset_bundle->printers.get_edited_preset();
|
||||
const auto& printer_cfg = preset.config;
|
||||
device_info.dev_ip = dev_ip;
|
||||
|
||||
device_info.dev_ip = dev_ip;
|
||||
device_info.api_key = password;
|
||||
device_info.model_name = printer_cfg.opt_string("printer_model");
|
||||
device_info.model_id = preset.get_printer_type(preset_bundle);
|
||||
device_info.base_url = use_ssl ? "https://" + dev_ip : "http://" + dev_ip;
|
||||
device_info.base_url = normalize_base_url(use_ssl, dev_ip, port);
|
||||
device_info.dev_id = dev_id;
|
||||
device_info.version = "";
|
||||
device_info.dev_name = device_info.dev_id;
|
||||
@@ -1301,7 +1251,45 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const
|
||||
void MoonrakerPrinterAgent::send_gcode_async(const std::string& dev_id, const std::string& gcode,
|
||||
std::function<void(bool)> on_result) const
|
||||
{
|
||||
(void) dev_id;
|
||||
const std::string base_url = device_info.base_url;
|
||||
const std::string api_key = device_info.api_key;
|
||||
auto http = Http::post(join_url(base_url, "/printer/gcode/script"));
|
||||
if (!api_key.empty()) {
|
||||
http.header("X-Api-Key", api_key);
|
||||
}
|
||||
http.header("Content-Type", "application/json")
|
||||
.set_post_body(nlohmann::json{{"script", gcode}}.dump())
|
||||
.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([on_result](std::string body, unsigned status_code) {
|
||||
(void) body;
|
||||
const bool success = status_code == 200;
|
||||
if (!success) {
|
||||
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: HTTP error " << status_code;
|
||||
}
|
||||
if (on_result) {
|
||||
on_result(success);
|
||||
}
|
||||
})
|
||||
.on_error([on_result](std::string body, std::string err, unsigned status_code) {
|
||||
(void) body;
|
||||
std::string error = err;
|
||||
if (status_code > 0) {
|
||||
error += " (HTTP " + std::to_string(status_code) + ")";
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: " << error;
|
||||
if (on_result) {
|
||||
on_result(false);
|
||||
}
|
||||
})
|
||||
.perform();
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::send_gcode_sync(const std::string& dev_id, const std::string& gcode) const
|
||||
{
|
||||
nlohmann::json payload;
|
||||
payload["script"] = gcode;
|
||||
@@ -2106,17 +2094,20 @@ bool MoonrakerPrinterAgent::upload_gcode(const std::string& local_path,
|
||||
|
||||
int MoonrakerPrinterAgent::pause_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "PAUSE") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
send_gcode_async(dev_id, "PAUSE");
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::resume_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "RESUME") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
send_gcode_async(dev_id, "RESUME");
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::cancel_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "CANCEL_PRINT") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
send_gcode_async(dev_id, "CANCEL_PRINT");
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::send_jsonrpc_command(const std::string& base_url,
|
||||
@@ -2183,13 +2174,13 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id,
|
||||
if (is_stale()) {
|
||||
return;
|
||||
}
|
||||
device_info.dev_name = fetched_info.dev_name;
|
||||
device_info.dev_name = fetched_info.dev_name.empty() ? dev_id : fetched_info.dev_name;
|
||||
device_info.version = fetched_info.version;
|
||||
device_info.klippy_state = fetched_info.klippy_state;
|
||||
}
|
||||
|
||||
// Orca todo: disable websocket for now, as we don't use MonitorPanel for Moonraker printers yet
|
||||
#if 0
|
||||
#if 1
|
||||
// Query initial status
|
||||
nlohmann::json initial_status;
|
||||
if (query_printer_status(base_url, api_key, initial_status, error_msg)) {
|
||||
@@ -2230,26 +2221,11 @@ bool MoonrakerPrinterAgent::is_numeric(const std::string& value)
|
||||
return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
|
||||
}
|
||||
|
||||
std::string MoonrakerPrinterAgent::normalize_base_url(std::string host, const std::string& port)
|
||||
std::string MoonrakerPrinterAgent::normalize_base_url(bool use_ssl, const std::string& host, const std::string& port)
|
||||
{
|
||||
boost::trim(host);
|
||||
if (host.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string value = host;
|
||||
if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) {
|
||||
value += ":" + port;
|
||||
}
|
||||
|
||||
if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) {
|
||||
value = "http://" + value;
|
||||
}
|
||||
|
||||
if (value.size() > 1 && value.back() == '/') {
|
||||
value.pop_back();
|
||||
}
|
||||
|
||||
std::string value = use_ssl ? "https://" : "http://";
|
||||
value += host;
|
||||
value += port.empty() ? "" : (":" + port);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,24 +28,15 @@ public:
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int connect_printer(const PrinterConnectionParams& params) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
// Certificates
|
||||
int check_cert() override;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override;
|
||||
|
||||
// Discovery
|
||||
bool start_discovery(bool start, bool sending) override;
|
||||
|
||||
// Binding
|
||||
int ping_bind(std::string ping_code) override;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
@@ -71,7 +62,7 @@ public:
|
||||
|
||||
// Pull-mode agent (on-demand filament sync)
|
||||
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; }
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
||||
|
||||
protected:
|
||||
struct MoonrakerDeviceInfo
|
||||
@@ -104,7 +95,7 @@ protected:
|
||||
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
|
||||
|
||||
// Methods that derived classes may need to override or access
|
||||
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
virtual bool init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port);
|
||||
virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
|
||||
|
||||
// State access for derived classes
|
||||
@@ -112,7 +103,7 @@ protected:
|
||||
|
||||
// Helpers
|
||||
bool is_numeric(const std::string& value);
|
||||
std::string normalize_base_url(std::string host, const std::string& port);
|
||||
std::string normalize_base_url(bool use_ssl, const std::string& host, const std::string& port);
|
||||
std::string sanitize_filename(const std::string& filename);
|
||||
std::string join_url(const std::string& base_url, const std::string& path) const;
|
||||
|
||||
@@ -129,7 +120,9 @@ private:
|
||||
|
||||
bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
|
||||
bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const;
|
||||
bool send_gcode(const std::string& dev_id, const std::string& gcode) const;
|
||||
bool send_gcode_sync(const std::string& dev_id, const std::string& gcode) const;
|
||||
void send_gcode_async(const std::string& dev_id, const std::string& gcode,
|
||||
std::function<void(bool)> on_result = {}) const;
|
||||
|
||||
void announce_printhost_device();
|
||||
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#include <stdio.h>
|
||||
#include "NetworkAgent.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -115,6 +114,8 @@ void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr<
|
||||
|
||||
void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent)
|
||||
{
|
||||
m_user_machine_list_generation.fetch_add(1);
|
||||
|
||||
// Disconnect all callbacks from the old agent
|
||||
auto old_printer_agent = m_printer_agent;
|
||||
|
||||
@@ -432,10 +433,26 @@ int NetworkAgent::check_user_task_report(int* task_id, bool* printable, const st
|
||||
|
||||
int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const std::string request_agent_id = m_printer_agent_id;
|
||||
const std::uint64_t request_generation = m_user_machine_list_generation.fetch_add(1) + 1;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_print_info(http_code, http_body);
|
||||
return -1;
|
||||
if (!cloud_agent)
|
||||
return -1;
|
||||
|
||||
const int result = cloud_agent->get_user_print_info(http_code, http_body);
|
||||
if (result == 0 && http_body) {
|
||||
try {
|
||||
nlohmann::json response = nlohmann::json::parse(*http_body);
|
||||
response["provider"] = provider;
|
||||
response["agent_id"] = request_agent_id;
|
||||
response["generation"] = request_generation;
|
||||
*http_body = response.dump();
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " metadata injection exception=" << e.what();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider)
|
||||
@@ -515,6 +532,15 @@ int NetworkAgent::get_camera_url(std::string dev_id, std::function<void(std::str
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<ICameraSignalingChannel>
|
||||
NetworkAgent::create_camera_signaling_channel(const std::string& dev_id, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->create_camera_signaling_channel(dev_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
@@ -767,10 +793,81 @@ int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
int NetworkAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl);
|
||||
return m_printer_agent->command_ams_refresh_rfid(dev_id, ams_id, slot_id, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_ams_calibrate(dev_id, ams_id, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_ams_select_tray(dev_id, tray_id, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_start_camera(std::string dev_id)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_start_camera(dev_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_xyz_abs(dev_id, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_auto_leveling(dev_id, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_go_home(dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_set_bed(dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_set_nozzle(dev_id, temp, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed,
|
||||
bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->command_axis_control(dev_id, axis, unit, input_val, speed, is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->connect_printer(params);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -788,6 +885,13 @@ int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_s
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::default_lan_username() const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->default_lan_username();
|
||||
return {};
|
||||
}
|
||||
|
||||
int NetworkAgent::check_cert()
|
||||
{
|
||||
if (m_printer_agent)
|
||||
@@ -846,8 +950,14 @@ std::string NetworkAgent::get_user_selected_machine()
|
||||
|
||||
int NetworkAgent::set_user_selected_machine(std::string dev_id)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_user_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: dev_id=" << dev_id
|
||||
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>");
|
||||
if (m_printer_agent) {
|
||||
const int result = m_printer_agent->set_user_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: result=" << result;
|
||||
return result;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::set_user_selected_machine: no printer agent";
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -867,15 +977,27 @@ int NetworkAgent::stop_subscribe(std::string module)
|
||||
|
||||
int NetworkAgent::add_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->add_subscribe(std::move(dev_list));
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: count=" << dev_list.size()
|
||||
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>");
|
||||
if (m_printer_agent) {
|
||||
const int result = m_printer_agent->add_subscribe(std::move(dev_list));
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: result=" << result;
|
||||
return result;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::add_subscribe: no printer agent";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::del_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->del_subscribe(std::move(dev_list));
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: count=" << dev_list.size()
|
||||
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>");
|
||||
if (m_printer_agent) {
|
||||
const int result = m_printer_agent->del_subscribe(std::move(dev_list));
|
||||
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: result=" << result;
|
||||
return result;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::del_subscribe: no printer agent";
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -921,10 +1043,10 @@ FilamentSyncMode NetworkAgent::get_filament_sync_mode() const
|
||||
return FilamentSyncMode::none;
|
||||
}
|
||||
|
||||
bool NetworkAgent::fetch_filament_info(std::string dev_id)
|
||||
bool NetworkAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode)
|
||||
{
|
||||
if (m_printer_agent) {
|
||||
return m_printer_agent->fetch_filament_info(dev_id);
|
||||
return m_printer_agent->fetch_filament_info(dev_id, sync_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -943,6 +1065,20 @@ std::string NetworkAgent::from_orca_filament_id(const std::string& orca_filament
|
||||
return orca_filament_id;
|
||||
}
|
||||
|
||||
CameraStreamMode NetworkAgent::get_camera_stream_mode() const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->get_camera_stream_mode();
|
||||
return CameraStreamMode::none;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_local_camera_stream_url() const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->get_camera_url();
|
||||
return {};
|
||||
}
|
||||
|
||||
int NetworkAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
#define __NETWORK_Agent_HPP__
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class IPrinterAgent;
|
||||
|
||||
// Forward declaration
|
||||
class BBLNetworkPlugin;
|
||||
|
||||
@@ -50,6 +56,7 @@ public:
|
||||
// Sub-agent accessors
|
||||
std::shared_ptr<ICloudServiceAgent> get_cloud_agent(const std::string& provider = ORCA_CLOUD_PROVIDER) const;
|
||||
std::shared_ptr<IPrinterAgent> get_printer_agent() const { return m_printer_agent; }
|
||||
std::uint64_t get_user_machine_list_generation() const { return m_user_machine_list_generation.load(); }
|
||||
|
||||
// Shared agent management
|
||||
void add_cloud_agent(const std::string& provider, std::shared_ptr<ICloudServiceAgent> agent);
|
||||
@@ -109,6 +116,7 @@ public:
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::unique_ptr<ICameraSignalingChannel> create_camera_signaling_channel(const std::string& dev_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
@@ -142,9 +150,21 @@ public:
|
||||
int set_on_local_message_fn(OnMessageFn fn);
|
||||
int set_server_callback(OnServerErrFn fn);
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag);
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode);
|
||||
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode);
|
||||
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode);
|
||||
int command_start_camera(std::string dev_id);
|
||||
int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode);
|
||||
int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode);
|
||||
int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode);
|
||||
int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode);
|
||||
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode);
|
||||
int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed,
|
||||
bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode);
|
||||
int connect_printer(const PrinterConnectionParams& params);
|
||||
int disconnect_printer();
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag);
|
||||
std::string default_lan_username() const;
|
||||
int check_cert();
|
||||
void install_device_cert(std::string dev_id, bool lan_only);
|
||||
bool start_discovery(bool start, bool sending);
|
||||
@@ -164,7 +184,9 @@ public:
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
FilamentSyncMode get_filament_sync_mode() const;
|
||||
bool fetch_filament_info(std::string dev_id);
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull);
|
||||
CameraStreamMode get_camera_stream_mode() const;
|
||||
std::string get_local_camera_stream_url() const;
|
||||
std::string to_orca_filament_id(const std::string& printer_filament_id) const;
|
||||
std::string from_orca_filament_id(const std::string& orca_filament_id) const;
|
||||
int request_bind_ticket(std::string* ticket);
|
||||
@@ -195,6 +217,7 @@ private:
|
||||
std::map<std::string, std::shared_ptr<ICloudServiceAgent>> m_cloud_agents;
|
||||
std::shared_ptr<IPrinterAgent> m_printer_agent;
|
||||
std::string m_printer_agent_id;
|
||||
std::atomic<std::uint64_t> m_user_machine_list_generation{0};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <slic3r/GUI/GUI_App.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
@@ -314,6 +316,21 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
|
||||
|
||||
std::shared_ptr<IPrinterAgent> cached_agent;
|
||||
|
||||
auto reject_conflicting_capability = [plugin_key, capability_name](const std::string& error_message) {
|
||||
if (!wxTheApp || GUI::wxGetApp().is_closing())
|
||||
return;
|
||||
GUI::wxGetApp().CallAfter([plugin_key, capability_name, error_message]() {
|
||||
if (GUI::wxGetApp().is_closing())
|
||||
return;
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
manager.set_plugin_error(plugin_key, error_message);
|
||||
// note: the unload callback triggered by disabling will call deregister,
|
||||
// which will be a no-op since the printer agent is never registered
|
||||
manager.set_capability_enabled({PluginCapabilityType::PrinterConnection, capability_name, plugin_key}, false);
|
||||
wxMessageBox(wxString::FromUTF8(error_message.c_str()), _L("Plugins"), wxOK | wxICON_WARNING, GUI::wxGetApp().GetTopWindow());
|
||||
});
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
|
||||
@@ -332,9 +349,10 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
|
||||
auto& python_agent_ids = get_python_printer_agent_ids();
|
||||
for (const auto& pair : python_agent_ids) {
|
||||
if (pair.first != capability_key && pair.second == info.id) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses duplicate agent ID '" << info.id
|
||||
<< "' already registered by capability '" << pair.first.second << "' from plugin '"
|
||||
<< pair.first.first << "'";
|
||||
const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id +
|
||||
"' is already registered by capability '" + pair.first.second + "' from plugin '" + pair.first.first + "'.";
|
||||
BOOST_LOG_TRIVIAL(warning) << error_message;
|
||||
reject_conflicting_capability(error_message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -354,12 +372,22 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
|
||||
|
||||
auto& agents = get_printer_agents();
|
||||
auto agent_it = agents.find(info.id);
|
||||
// why: reject only when the ID is owned by SOMEONE ELSE - a built-in has an empty
|
||||
// plugin_identifier, another plugin/capability has a different plugin_full_ref. When it
|
||||
// IS the same plugin_full_ref, this capability is just re-registering itself, so fall
|
||||
// through and refresh.
|
||||
if (agent_it != agents.end() && agent_it->second.plugin_identifier != plugin_full_ref) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses agent ID '" << info.id
|
||||
<< "' already registered by '" << agent_it->second.display_name << "'";
|
||||
const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id +
|
||||
"' is already registered by '" + agent_it->second.display_name + "'.";
|
||||
BOOST_LOG_TRIVIAL(warning) << error_message;
|
||||
reject_conflicting_capability(error_message);
|
||||
return;
|
||||
}
|
||||
|
||||
// why: insert_or_assign, not emplace - reaching here means the ID is new, or the same
|
||||
// capability is re-registering (same plugin_full_ref). In the re-register case we WANT to
|
||||
// overwrite so the factory closure points at the current live capability instance; emplace
|
||||
// would silently keep the stale entry.
|
||||
agents.insert_or_assign(info.id, PrinterAgentInfo(info.id, info.name, plugin_full_ref, std::move(factory)));
|
||||
python_agent_ids[capability_key] = info.id;
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ constexpr const char* ORCA_UNSUBSCRIBE_PLUGINS = "/api/v1/plugins/subscriptions"
|
||||
constexpr const char* ORCA_PLUGINS_MINE = "/api/v1/plugins/mine";
|
||||
constexpr const char* ORCA_PLUGINS_BASE = "/api/v1/plugins";
|
||||
constexpr const char* ORCA_PLUGIN_DOWNLOAD_URL = "/api/v1/plugins/download";
|
||||
constexpr const char* ORCA_CLOUD_PRINTER = "/api/v1/printers";
|
||||
|
||||
constexpr const char* ORCA_CLOUD_LOGIN_PATH = "/orcaslicer-login";
|
||||
|
||||
@@ -2623,11 +2624,51 @@ int OrcaCloudServiceAgent::check_user_task_report(int* task_id, bool* printable)
|
||||
|
||||
int OrcaCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_print_info (stub)";
|
||||
std::string response;
|
||||
unsigned int code = 0;
|
||||
int result = http_get(ORCA_CLOUD_PRINTER, &response, &code);
|
||||
|
||||
if (http_code)
|
||||
*http_code = 200;
|
||||
if (http_body)
|
||||
*http_body = "{}";
|
||||
*http_code = code;
|
||||
|
||||
if (result != 0 || code != 200)
|
||||
return result != 0 ? result : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
|
||||
|
||||
try {
|
||||
auto resp_json = nlohmann::json::parse(response);
|
||||
nlohmann::json devices = nlohmann::json::array();
|
||||
|
||||
for (const auto& printer : resp_json.value("data", nlohmann::json::array())) {
|
||||
const std::string role = printer.value("access_role", "");
|
||||
if (role.empty() || role == "viewer")
|
||||
continue;
|
||||
|
||||
nlohmann::json device;
|
||||
device["dev_id"] = printer.value("id", "");
|
||||
device["dev_name"] = printer.value("name", "");
|
||||
if (printer.contains("model") && printer["model"].is_string())
|
||||
device["dev_model_name"] = printer["model"].get<std::string>();
|
||||
|
||||
bool online = false;
|
||||
if (printer.contains("status_snapshot") && printer["status_snapshot"].is_object()) {
|
||||
const auto& status = printer["status_snapshot"].value("status", nlohmann::json::object());
|
||||
online = status.value("connection", nlohmann::json::object()).value("state", "") == "online";
|
||||
if (status.contains("job") && status["job"].is_object())
|
||||
device["task_status"] = status["job"].value("state", "");
|
||||
}
|
||||
device["dev_online"] = online;
|
||||
devices.push_back(std::move(device));
|
||||
}
|
||||
|
||||
if (http_body) {
|
||||
nlohmann::json out;
|
||||
out["devices"] = std::move(devices);
|
||||
*http_body = out.dump();
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
|
||||
}
|
||||
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef __ORCA_CLOUD_SERVICE_AGENT_HPP__
|
||||
#define __ORCA_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "ICameraSignalingChannel.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
@@ -241,6 +242,7 @@ public:
|
||||
// ICloudServiceAgent Interface Implementation - Model Mall & Publishing
|
||||
// ========================================================================
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override;
|
||||
// std::unique_ptr<ICameraSignalingChannel> create_camera_signaling_channel(const std::string& dev_id) override;
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) override;
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override;
|
||||
int get_model_publish_url(std::string* url) override;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "OrcaPrinterAgent.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -26,7 +27,7 @@ int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
int OrcaPrinterAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
@@ -41,19 +42,6 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Certificates - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::check_cert()
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Discovery - Stub
|
||||
// ============================================================================
|
||||
@@ -63,47 +51,11 @@ bool OrcaPrinterAgent::start_discovery(bool start, bool sending)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Binding - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::bind(
|
||||
std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::unbind(std::string dev_id)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (ticket)
|
||||
*ticket = "";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
|
||||
{
|
||||
// No BBL cloud snapshot source; report failure so the caller falls back.
|
||||
(void) dev_id;
|
||||
(void) file_name;
|
||||
(void) callback;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
|
||||
@@ -28,24 +28,15 @@ public:
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int connect_printer(const PrinterConnectionParams& params) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
// Certificates
|
||||
int check_cert() override;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override;
|
||||
|
||||
// Discovery
|
||||
bool start_discovery(bool start, bool sending) override;
|
||||
|
||||
// Binding
|
||||
int ping_bind(std::string ping_code) override;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum LiveviewLocal {
|
||||
LVL_None,
|
||||
LVL_Disable,
|
||||
LVL_Local,
|
||||
LVL_Rtsps,
|
||||
LVL_Rtsp
|
||||
};
|
||||
|
||||
enum LiveviewRemote {
|
||||
LVR_None,
|
||||
LVR_Tutk,
|
||||
LVR_Agora,
|
||||
LVR_TutkAgora
|
||||
};
|
||||
|
||||
enum FileLocal {
|
||||
FL_None,
|
||||
FL_Local
|
||||
};
|
||||
|
||||
enum FileRemote {
|
||||
FR_None,
|
||||
FR_Tutk,
|
||||
FR_Agora,
|
||||
FR_TutkAgora
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -40,7 +40,7 @@ AgentInfo QidiPrinterAgent::get_agent_info_static()
|
||||
return AgentInfo{"qidi", "Qidi", QidiPrinterAgent_VERSION, "Qidi printer agent"};
|
||||
}
|
||||
|
||||
bool QidiPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/)
|
||||
{
|
||||
std::string error;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
// Override filament sync (Qidi-specific implementation)
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
||||
|
||||
private:
|
||||
struct QidiFilamentDict
|
||||
|
||||
@@ -104,7 +104,7 @@ std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type
|
||||
return base;
|
||||
}
|
||||
|
||||
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/)
|
||||
{
|
||||
std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect");
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
||||
|
||||
private:
|
||||
// Combine filament_type + filament_sub_type into a unified type string
|
||||
|
||||
@@ -23,6 +23,14 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
.value("Pull", FilamentSyncMode::pull)
|
||||
.export_values();
|
||||
|
||||
py::enum_<CameraStreamMode>(printer_agent_module, "CameraStreamMode")
|
||||
.value("None_", CameraStreamMode::none)
|
||||
.value("HTTP", CameraStreamMode::http)
|
||||
.value("HTTPS", CameraStreamMode::https)
|
||||
.value("HTTP_SNAPSHOT", CameraStreamMode::http_snapshot)
|
||||
.value("RTSP", CameraStreamMode::rtsp)
|
||||
.export_values();
|
||||
|
||||
py::class_<AgentInfo>(printer_agent_module, "AgentInfo")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::string id, std::string name, std::string version, std::string description) {
|
||||
@@ -89,6 +97,16 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
.def_readwrite("task_ext_change_assist", &PrintParams::task_ext_change_assist)
|
||||
.def_readwrite("try_emmc_print", &PrintParams::try_emmc_print);
|
||||
|
||||
py::class_<PrinterConnectionParams>(printer_agent_module, "PrinterConnectionParams")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("dev_id", &PrinterConnectionParams::dev_id)
|
||||
.def_readwrite("host", &PrinterConnectionParams::host)
|
||||
.def_readwrite("port", &PrinterConnectionParams::port)
|
||||
.def_readwrite("username", &PrinterConnectionParams::username)
|
||||
.def_readwrite("password", &PrinterConnectionParams::password)
|
||||
.def_readwrite("use_ssl", &PrinterConnectionParams::use_ssl)
|
||||
.def_readwrite("ca_file", &PrinterConnectionParams::ca_file);
|
||||
|
||||
py::class_<PrinterAgentPluginCapability, PluginCapabilityInterface, PyPrinterAgentPluginCapabilityTrampoline, std::shared_ptr<PrinterAgentPluginCapability>>(
|
||||
printer_agent_module, "PrinterAgentBase")
|
||||
.def(py::init<>())
|
||||
@@ -98,6 +116,16 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
.def("disconnect_printer", &PrinterAgentPluginCapability::disconnect_printer)
|
||||
.def("send_message", &PrinterAgentPluginCapability::send_message)
|
||||
.def("send_message_to_printer", &PrinterAgentPluginCapability::send_message_to_printer)
|
||||
.def("command_ams_refresh_rfid", &PrinterAgentPluginCapability::command_ams_refresh_rfid)
|
||||
.def("command_ams_calibrate", &PrinterAgentPluginCapability::command_ams_calibrate)
|
||||
.def("command_ams_select_tray", &PrinterAgentPluginCapability::command_ams_select_tray)
|
||||
.def("command_start_camera", &PrinterAgentPluginCapability::command_start_camera)
|
||||
.def("command_xyz_abs", &PrinterAgentPluginCapability::command_xyz_abs)
|
||||
.def("command_auto_leveling", &PrinterAgentPluginCapability::command_auto_leveling)
|
||||
.def("command_go_home", &PrinterAgentPluginCapability::command_go_home)
|
||||
.def("command_set_bed", &PrinterAgentPluginCapability::command_set_bed)
|
||||
.def("command_set_nozzle", &PrinterAgentPluginCapability::command_set_nozzle)
|
||||
.def("command_axis_control", &PrinterAgentPluginCapability::command_axis_control)
|
||||
.def("start_discovery", &PrinterAgentPluginCapability::start_discovery)
|
||||
.def("bind_detect", &PrinterAgentPluginCapability::bind_detect)
|
||||
.def("get_user_selected_machine", &PrinterAgentPluginCapability::get_user_selected_machine)
|
||||
@@ -105,7 +133,9 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
.def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard)
|
||||
.def("start_local_print", &PrinterAgentPluginCapability::start_local_print)
|
||||
.def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode)
|
||||
.def("get_camera_stream_mode", &PrinterAgentPluginCapability::get_camera_stream_mode)
|
||||
.def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info)
|
||||
.def("get_camera_url", &PrinterAgentPluginCapability::get_camera_url)
|
||||
.def("check_cert", &PrinterAgentPluginCapability::check_cert)
|
||||
.def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert)
|
||||
.def("ping_bind", &PrinterAgentPluginCapability::ping_bind)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -24,60 +23,39 @@ public:
|
||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }
|
||||
|
||||
// set_cloud_agent is the host-managed dependency injection point — the host hands the
|
||||
// capability its ICloudServiceAgent — so it is the one operation kept native here. Every
|
||||
// other IPrinterAgent operation is pure: the Python plugin must implement all of them.
|
||||
// capability its ICloudServiceAgent — so it is the one operation kept native here. Optional
|
||||
// operations inherit IPrinterAgent's defaults; the remaining operations are plugin-defined.
|
||||
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) final override { (void) cloud; }
|
||||
|
||||
AgentInfo get_agent_info() override = 0;
|
||||
|
||||
int connect_printer(
|
||||
std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override = 0;
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
|
||||
bool start_discovery(bool start, bool sending) override = 0;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0;
|
||||
std::string get_user_selected_machine() override = 0;
|
||||
int set_user_selected_machine(std::string dev_id) override = 0;
|
||||
int start_send_gcode_to_sdcard(PrintParams params,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn,
|
||||
OnWaitFn wait_fn) override = 0;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
||||
FilamentSyncMode get_filament_sync_mode() const override = 0;
|
||||
bool fetch_filament_info(std::string dev_id) override = 0;
|
||||
int connect_printer(const PrinterConnectionParams& params) override = 0;
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
|
||||
bool start_discovery(bool start, bool sending) override = 0;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0;
|
||||
std::string get_user_selected_machine() override = 0;
|
||||
int set_user_selected_machine(std::string dev_id) override = 0;
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
||||
|
||||
int check_cert() override = 0;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override = 0;
|
||||
int ping_bind(std::string ping_code) override = 0;
|
||||
int bind(std::string dev_ip,
|
||||
std::string dev_id,
|
||||
std::string dev_model,
|
||||
std::string sec_link,
|
||||
std::string timezone,
|
||||
bool improved,
|
||||
OnUpdateStatusFn update_fn) override = 0;
|
||||
int unbind(std::string dev_id) override = 0;
|
||||
// request_bind_ticket has a std::string* out-param that cannot round-trip through a
|
||||
// pybind11 override directly; the trampoline wraps it (the Python plugin returns a
|
||||
// (result, ticket) tuple), so it stays pure here like the rest.
|
||||
int request_bind_ticket(std::string* ticket) override = 0;
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override = 0;
|
||||
int set_server_callback(OnServerErrFn fn) override = 0;
|
||||
int set_server_callback(OnServerErrFn fn) override = 0;
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0;
|
||||
int start_local_print_with_record(PrintParams params,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn,
|
||||
OnWaitFn wait_fn) override = 0;
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
||||
OnWaitFn wait_fn) override = 0;
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
||||
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override = 0;
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override = 0;
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override = 0;
|
||||
int set_on_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_on_user_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0;
|
||||
int set_on_local_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override = 0;
|
||||
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override = 0;
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override = 0;
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override = 0;
|
||||
int set_on_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_on_user_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0;
|
||||
int set_on_local_message_fn(OnMessageFn fn) override = 0;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override = 0;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
+84
-8
@@ -5,7 +5,10 @@
|
||||
#include "../../PyPluginTrampoline.hpp"
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include <slic3r/plugin/PluginAuditManager.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <string>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
@@ -22,6 +25,12 @@
|
||||
} ORCA_PY_AGENT_CATCH(name) \
|
||||
return printer_agent_failure<ret>()
|
||||
|
||||
#define ORCA_PY_AGENT_OVERRIDE_DEFAULT(ret, name, ...) \
|
||||
try { \
|
||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, ret, PrinterAgentPluginCapability, name, ##__VA_ARGS__); \
|
||||
} ORCA_PY_AGENT_CATCH(name) \
|
||||
return printer_agent_failure<ret>()
|
||||
|
||||
namespace Slic3r {
|
||||
// NetworkAgent's no-agent answer: -1 for a status code, the empty value (false, "", none) otherwise.
|
||||
template<typename T> T printer_agent_failure()
|
||||
@@ -42,9 +51,9 @@ public:
|
||||
ORCA_PY_AGENT_OVERRIDE(AgentInfo, get_agent_info);
|
||||
}
|
||||
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override
|
||||
int connect_printer(const PrinterConnectionParams& params) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(int, connect_printer, dev_id, dev_ip, username, password, use_ssl);
|
||||
ORCA_PY_AGENT_OVERRIDE(int, connect_printer, params);
|
||||
}
|
||||
|
||||
int disconnect_printer() override
|
||||
@@ -62,6 +71,61 @@ public:
|
||||
ORCA_PY_AGENT_OVERRIDE(int, send_message_to_printer, dev_id, json_str, qos, flag);
|
||||
}
|
||||
|
||||
int command_ams_refresh_rfid(std::string dev_id, int ams_id,int slot_id, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_refresh_rfid, dev_id, ams_id, slot_id, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_calibrate, dev_id, ams_id, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_select_tray, dev_id, tray_id, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_start_camera(std::string dev_id) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_start_camera, dev_id);
|
||||
}
|
||||
|
||||
int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_xyz_abs, dev_id, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_auto_leveling, dev_id, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing,
|
||||
int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_go_home, dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl,
|
||||
int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_bed, dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_nozzle, dev_id, temp, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val,
|
||||
int speed, bool is_core_xy, bool supports_mqtt_axis_control,
|
||||
int sequence_id, bool lan_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_axis_control, dev_id, axis, unit, input_val, speed,
|
||||
is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode);
|
||||
}
|
||||
|
||||
bool start_discovery(bool start, bool sending) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(bool, start_discovery, start, sending);
|
||||
@@ -95,12 +159,22 @@ public:
|
||||
|
||||
FilamentSyncMode get_filament_sync_mode() const override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(FilamentSyncMode, get_filament_sync_mode);
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(FilamentSyncMode, get_filament_sync_mode);
|
||||
}
|
||||
|
||||
bool fetch_filament_info(std::string dev_id) override
|
||||
CameraStreamMode get_camera_stream_mode() const override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(bool, fetch_filament_info, dev_id);
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(CameraStreamMode, get_camera_stream_mode);
|
||||
}
|
||||
|
||||
std::string get_camera_url() const override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(std::string, get_camera_url);
|
||||
}
|
||||
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(bool, fetch_filament_info, dev_id, sync_mode);
|
||||
}
|
||||
|
||||
int check_cert() override
|
||||
@@ -110,7 +184,7 @@ public:
|
||||
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(void, install_device_cert, dev_id, lan_only);
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(void, install_device_cert, dev_id, lan_only);
|
||||
}
|
||||
|
||||
int ping_bind(std::string ping_code) override
|
||||
@@ -145,7 +219,7 @@ public:
|
||||
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override
|
||||
{
|
||||
ORCA_PY_AGENT_OVERRIDE(int, get_hms_snapshot, dev_id, file_name, callback);
|
||||
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, get_hms_snapshot, dev_id, file_name, callback);
|
||||
}
|
||||
|
||||
int set_server_callback(OnServerErrFn fn) override
|
||||
@@ -196,6 +270,8 @@ public:
|
||||
// request_bind_ticket returns its ticket through a std::string* out-param, which pybind11
|
||||
// cannot marshal back through a plain override. We dispatch manually: the Python plugin
|
||||
// returns a (result, ticket) tuple, which we unpack into the int result and the out-param.
|
||||
// Not required to be implemented, so a missing override answers with the same failure value
|
||||
// as the other printer-agent operations. Leave the out-param untouched on failure.
|
||||
int request_bind_ticket(std::string* ticket) override
|
||||
{
|
||||
try {
|
||||
@@ -207,7 +283,7 @@ public:
|
||||
pybind11::function override =
|
||||
pybind11::get_override(static_cast<const PrinterAgentPluginCapability*>(this), "request_bind_ticket");
|
||||
if (!override)
|
||||
pybind11::pybind11_fail("Tried to call pure virtual function \"PrinterAgentPluginCapability::request_bind_ticket\"");
|
||||
return printer_agent_failure<int>();
|
||||
try {
|
||||
pybind11::tuple result = override().cast<pybind11::tuple>();
|
||||
if (ticket)
|
||||
|
||||
Reference in New Issue
Block a user