Merge impl branch

This commit is contained in:
Lam Wei Lun
2026-09-24 14:46:30 +08:00
1163 changed files with 28054 additions and 6833 deletions
+13 -2
View File
@@ -28,6 +28,7 @@
#include <csignal>
#include <atomic>
#include <new>
#include <optional>
#if defined(__linux__) || defined(__LINUX__)
#include <condition_variable>
@@ -1614,6 +1615,10 @@ int CLI::run(int argc, char **argv)
ConfigOptionBool* allow_rotations_option = m_config.option<ConfigOptionBool>("allow_rotations");
if (allow_rotations_option)
allow_rotations = allow_rotations_option->value;
// Only an explicit --align-to-y-axis overrides the printer-structure default.
std::optional<bool> align_to_y_axis;
if (m_given_option_keys.count("align_to_y_axis") > 0)
align_to_y_axis = m_config.opt_bool("align_to_y_axis");
ConfigOptionBool* skip_modified_gcodes_option = m_config.option<ConfigOptionBool>("skip_modified_gcodes");
if (skip_modified_gcodes_option)
@@ -5298,7 +5303,9 @@ int CLI::run(int argc, char **argv)
arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT;
arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT;
}
if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
if (align_to_y_axis.has_value()) {
arrange_cfg.align_to_y_axis = *align_to_y_axis;
} else if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
}
@@ -5748,7 +5755,9 @@ int CLI::run(int argc, char **argv)
arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT;
arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT;
}
if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
if (align_to_y_axis.has_value()) {
arrange_cfg.align_to_y_axis = *align_to_y_axis;
} else if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
}
@@ -7943,6 +7952,8 @@ bool CLI::setup(int argc, char **argv)
for (std::string &input_file : m_input_files)
input_file = resolve_cli_input_path(input_file);
m_given_option_keys.insert(opt_order.begin(), opt_order.end());
// Parse actions and transform options.
for (auto const &opt_key : opt_order) {
if (cli_actions_config_def.has(opt_key))
+4
View File
@@ -1,6 +1,8 @@
#ifndef SLIC3R_HPP
#define SLIC3R_HPP
#include <set>
#include "libslic3r/Config.hpp"
#include "libslic3r/Model.hpp"
@@ -113,6 +115,8 @@ private:
std::vector<std::string> m_input_files;
std::vector<std::string> m_actions;
std::vector<std::string> m_transforms;
// Options the user typed; setup() fills the CLI's own options with defaults afterwards.
std::set<std::string> m_given_option_keys;
std::vector<Model> m_models;
bool setup(int argc, char **argv);
+9
View File
@@ -224,6 +224,12 @@ void AppConfig::set_defaults()
set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99))));
}
// ORCA: view type the G-code preview opens with. "auto" keeps the automatic choice (Filament for
// multi material prints, Line Type for single material ones), "last" restores the view type the user
// picked last, any other value is a fixed view type name, see GCodeViewer::view_type_to_config_name().
if (get("preview_default_view_type").empty())
set("preview_default_view_type", "auto");
if (get("filaments_area_preferred_count").empty())
set("filaments_area_preferred_count", "10");
@@ -303,6 +309,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_REALISTIC_PHONG).empty())
set_bool(SETTING_OPENGL_REALISTIC_PHONG, true);
if (get(SETTING_OPENGL_REALISTIC_PREVIEW).empty())
set_bool(SETTING_OPENGL_REALISTIC_PREVIEW, false);
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
+1
View File
@@ -42,6 +42,7 @@ using namespace nlohmann;
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview"
#define SETTING_PLUGIN_PAGES_VISIBLE_COUNT "plugin_pages_visible_count"
#define PLUGIN_PAGES_VISIBLE_COUNT_MIN 1
+51 -5
View File
@@ -8,6 +8,7 @@
#include "I18N.hpp"
#include "GCode.hpp"
#include "Exception.hpp"
#include "LifecycleEvents.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
#include "Geometry/ConvexHull.hpp"
@@ -2476,6 +2477,15 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_writer.set_is_bbl_machine(print->is_BBL_printer());
print->set_started(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
ctx.cancellation_check = [print]() { return print->canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
// check if any custom gcode contains keywords used by the gcode processor to
// produce time estimation and gcode toolpaths
std::vector<std::pair<std::string, std::string>> validation_res = DoExport::validate_custom_gcode(*print);
@@ -2511,12 +2521,23 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_processor.set_print(print);
GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor);
if (! file.is_open()) {
BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl;
std::string err_msg = std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n";
BOOST_LOG_TRIVIAL(error) << err_msg << std::endl;
if (!fs::exists(folder)) {
//fs::create_directory(folder);
BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl;
std::string add_err_msg = "the parent path " + folder.string() +" is not there!!!";
BOOST_LOG_TRIVIAL(error) << add_err_msg << std::endl;
err_msg += add_err_msg;
}
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + err_msg;
ctx.cancellation_check = [print]() { return print->canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(err_msg);
}
try {
@@ -2527,11 +2548,19 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
boost::nowide::remove(path_tmp.c_str());
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
}
} catch (std::exception & /* ex */) {
} catch (std::exception &ex) {
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
// Close and remove the file.
file.close();
boost::nowide::remove(path_tmp.c_str());
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + ex.what();
ctx.cancellation_check = [print]() { return print->canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw;
}
file.close();
@@ -2637,6 +2666,14 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
std::error_code ret = rename_file(path_tmp, path);
if (ret) {
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
ctx.cancellation_check = [print]() { return print->canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' +
"Is " + path_tmp + " locked?" + '\n');
@@ -2647,7 +2684,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
print->set_done(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
ctx.cancellation_check = [print]() { return print->canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
// Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT /
// M486), which is driven by exclude_object for every printer
if(result != nullptr)
+249
View File
@@ -0,0 +1,249 @@
#pragma once
// LifecycleEvents.hpp
// --------------------
// Application lifecycle events (project, slicing, plate editing, preset, printer connection, and
// job activity) that other subsystems -- chiefly the plugin layer above libslic3r -- may want to
// observe. Lives in libslic3r rather than the plugin layer because some events fire from inside
// the slicing engine itself; see fire_lifecycle_event() below.
#include <functional>
#include <condition_variable>
#include <cstddef>
#include <mutex>
#include <string>
#include <utility>
namespace Slic3r
{
enum class LifecycleEvent {
// Project (3mf)
NewProject,
ProjectOpened,
ProjectBeforeSave,
ProjectAfterSave,
ProjectClosed,
ProjectDirtyChanged,
// Slicing pipeline
SliceStarted,
SliceGeometryFinished,
GCodeExportStarted,
GCodeExportFinished,
SlicingJobComplete,
// Plate/model editing
ObjectAdded,
ObjectDeleted,
ObjectTransformed,
ObjectChanged,
ObjectRenamed,
PlateCreated,
PlateDeleted,
PlateSelected,
PlateRenamed,
// Preset
PresetSelected,
PresetSaved,
// Printer/device
PrintStateChanged,
DeviceOnline,
DeviceOffline,
DeviceDiscovered,
DeviceSelected,
UploadStarted,
UploadFinished,
// Print/send jobs
PrintJobStarted,
PrintJobFinished,
SendJobStarted,
SendJobFinished,
};
// Scoped so callers must qualify (LifecycleEvtCode::Error, not ERROR) -- ERROR/OK collide with
// Windows macros (wingdi.h) as unqualified names.
enum class LifecycleEvtCode { Ok, Error, Warn };
struct LifecycleEventContext
{
// The primary subject identifier for the event. This is event-specific (for example, a
// project/output path, preset name, device id, or object name), must not contain status or
// prose, and may be empty when the event has no single subject.
std::string name;
// Outcome of the operation represented by the event. For state-change and start events,
// Ok means that the event occurred; it does not imply that a future operation succeeded.
LifecycleEvtCode code = LifecycleEvtCode::Ok;
// Optional human-readable detail or diagnostic text. It is not a stable parsing contract;
// machine-readable data should be represented by a dedicated field or event instead.
std::string msg;
// Stable subject/object identifier, when the source model provides one.
std::string id;
// Previous value for rename and other before/after events.
std::string previous_name;
// Device identifier for printer and job events.
std::string device_id;
// Job identifier when the originating queue/task provides one.
std::string job_id;
// Source subsystem or operation detail, suitable for filtering but not guaranteed to be
// exhaustive across versions.
std::string source;
// Plate, object, or volume index when the source uses an index rather than a stable id.
int index = -1;
// Aggregate project dirty state for ProjectDirtyChanged.
bool dirty = false;
// Optional host-side cancellation probe. Background slicing and G-code export events set
// this to the originating Print's cancellation state so dispatch can stop before calling
// the next capability. It is intentionally not exposed through the Python payload API.
std::function<bool()> cancellation_check;
};
inline std::string lifecycle_event_to_string(LifecycleEvent event)
{
switch (event) {
case LifecycleEvent::NewProject: return "NewProject";
case LifecycleEvent::ProjectOpened: return "ProjectOpened";
case LifecycleEvent::ProjectBeforeSave: return "ProjectBeforeSave";
case LifecycleEvent::ProjectAfterSave: return "ProjectAfterSave";
case LifecycleEvent::ProjectClosed: return "ProjectClosed";
case LifecycleEvent::ProjectDirtyChanged: return "ProjectDirtyChanged";
case LifecycleEvent::SliceStarted: return "SliceStarted";
case LifecycleEvent::SliceGeometryFinished: return "SliceGeometryFinished";
case LifecycleEvent::GCodeExportStarted: return "GCodeExportStarted";
case LifecycleEvent::GCodeExportFinished: return "GCodeExportFinished";
case LifecycleEvent::SlicingJobComplete: return "SlicingJobComplete";
case LifecycleEvent::ObjectAdded: return "ObjectAdded";
case LifecycleEvent::ObjectDeleted: return "ObjectDeleted";
case LifecycleEvent::ObjectTransformed: return "ObjectTransformed";
case LifecycleEvent::ObjectChanged: return "ObjectChanged";
case LifecycleEvent::ObjectRenamed: return "ObjectRenamed";
case LifecycleEvent::PlateCreated: return "PlateCreated";
case LifecycleEvent::PlateDeleted: return "PlateDeleted";
case LifecycleEvent::PlateSelected: return "PlateSelected";
case LifecycleEvent::PlateRenamed: return "PlateRenamed";
case LifecycleEvent::PresetSelected: return "PresetSelected";
case LifecycleEvent::PresetSaved: return "PresetSaved";
case LifecycleEvent::PrintStateChanged: return "PrintStateChanged";
case LifecycleEvent::DeviceOnline: return "DeviceOnline";
case LifecycleEvent::DeviceOffline: return "DeviceOffline";
case LifecycleEvent::DeviceDiscovered: return "DeviceDiscovered";
case LifecycleEvent::DeviceSelected: return "DeviceSelected";
case LifecycleEvent::UploadStarted: return "UploadStarted";
case LifecycleEvent::UploadFinished: return "UploadFinished";
case LifecycleEvent::PrintJobStarted: return "PrintJobStarted";
case LifecycleEvent::PrintJobFinished: return "PrintJobFinished";
case LifecycleEvent::SendJobStarted: return "SendJobStarted";
case LifecycleEvent::SendJobFinished: return "SendJobFinished";
default: return "Unknown";
}
}
inline std::string lifecycle_evt_code_to_string(LifecycleEvtCode code)
{
switch (code) {
case LifecycleEvtCode::Ok: return "Ok";
case LifecycleEvtCode::Error: return "Error";
case LifecycleEvtCode::Warn: return "Warn";
default: return "Unknown";
}
}
// Global cross-layer seam (mirrors ConfigBase::set_resolve_capability_fn): any libslic3r code can
// fire a lifecycle event without depending on the plugin layer above it, which installs the
// dispatcher here at startup. Not tied to Print/GCode specifically, since nothing here should
// require callers to hold a Print& just to report an event.
using LifecycleHookFn = std::function<void(LifecycleEvent, const LifecycleEventContext&)>;
namespace detail {
struct LifecycleHookState
{
std::mutex mutex;
std::condition_variable cv;
LifecycleHookFn fn;
std::size_t active_dispatches = 0;
bool accepting = false;
};
inline LifecycleHookState& lifecycle_hook_state()
{
static LifecycleHookState state;
return state;
}
class LifecycleDispatchGuard
{
public:
explicit LifecycleDispatchGuard(LifecycleHookState& state) : m_state(state) {}
~LifecycleDispatchGuard()
{
std::lock_guard<std::mutex> lock(m_state.mutex);
--m_state.active_dispatches;
if (m_state.active_dispatches == 0)
m_state.cv.notify_all();
}
LifecycleDispatchGuard(const LifecycleDispatchGuard&) = delete;
LifecycleDispatchGuard& operator=(const LifecycleDispatchGuard&) = delete;
private:
LifecycleHookState& m_state;
};
} // namespace detail
// Installing a hook starts accepting dispatches. Passing an empty function stops accepting
// new dispatches, detaches the hook, and waits for callbacks already in progress to finish.
// This is used during plugin shutdown so plugin code cannot be unloaded while a lifecycle
// callback is still executing. The empty-function path must not be called from inside the
// lifecycle callback itself.
inline void set_lifecycle_hook_fn(LifecycleHookFn fn)
{
detail::LifecycleHookState& state = detail::lifecycle_hook_state();
if (fn) {
std::lock_guard<std::mutex> lock(state.mutex);
state.fn = std::move(fn);
state.accepting = true;
return;
}
std::unique_lock<std::mutex> lock(state.mutex);
state.accepting = false;
state.fn = nullptr;
state.cv.wait(lock, [&state] { return state.active_dispatches == 0; });
}
inline void fire_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx)
{
detail::LifecycleHookState& state = detail::lifecycle_hook_state();
LifecycleHookFn fn;
{
std::lock_guard<std::mutex> lock(state.mutex);
if (!state.accepting || !state.fn)
return;
fn = state.fn;
++state.active_dispatches;
}
detail::LifecycleDispatchGuard guard(state);
fn(event, ctx);
}
}
+10
View File
@@ -47,6 +47,7 @@
#include <boost/log/trivial.hpp>
#include "libslic3r.h"
#include "LifecycleEvents.hpp"
#include "Utils.hpp"
#include "Time.hpp"
#include "PlaceholderParser.hpp"
@@ -2972,6 +2973,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
auto it = this->find_preset_internal(new_name);
const bool preset_existed = (it != m_presets.end() && it->name == new_name);
if (it != m_presets.end() && it->name == new_name) {
// Preset with the same name found.
Preset &preset = *it;
@@ -3079,6 +3081,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
this->get_selected_preset().save(&(parent_preset->config));
else
this->get_selected_preset().save(nullptr);
{
LifecycleEventContext ctx;
ctx.name = new_name;
ctx.msg = preset_existed ? "overwrite" : "new";
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::PresetSaved, ctx);
}
}
// A detached standalone preset for the Full Publish receiver: create a user preset holding
+44 -1
View File
@@ -14,6 +14,7 @@
#include "Flow.hpp"
#include "Geometry/ConvexHull.hpp"
#include "I18N.hpp"
#include "LifecycleEvents.hpp"
#include "ShortestPath.hpp"
#include "Thread.hpp"
#include "Time.hpp"
@@ -2694,6 +2695,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (m_objects.empty())
return;
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.cancellation_check = [this]() { return canceled(); };
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
}
for (PrintObject *obj : m_objects)
obj->clear_shared_object();
@@ -3312,6 +3321,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.cancellation_check = [this]() { return canceled(); };
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
}
}
// G-code export process, running at a background thread.
@@ -4911,6 +4928,15 @@ void Print::set_gcode_file_invalidated()
//BBS: add gcode file preload logic
void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
{
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
ctx.cancellation_check = [this]() { return canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
try {
GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
@@ -4930,13 +4956,30 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
*result = std::move(processor.extract_result());
result->filament_change_sequence = filament_seq_loaded;
result->nozzle_change_sequence = nozzle_seq_loaded;
} catch (std::exception & /* ex */) {
} catch (std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = file + "\n" + ex.what();
ctx.cancellation_check = [this]() { return canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(
std::string("Failed to process the G-code file ") + file + " from previous 3mf\n");
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully")%file.c_str();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
ctx.cancellation_check = [this]() { return canceled(); };
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
}
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
+6
View File
@@ -12347,6 +12347,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->tooltip = L("If enabled, Arrange will allow rotation when placing objects.");
def->set_default_value(new ConfigOptionBool(true));
def = this->add("align_to_y_axis", coBool);
def->label = L("Align to Y axis when arranging");
def->tooltip = L("If enabled, Arrange will turn each object so its long side runs along the Y axis before placing it. "
"When not given, it is on for i3 printers and off for the others, as in the GUI.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("avoid_extrusion_cali_region", coBool);
def->label = L("Avoid extrusion calibrate region when arranging");
def->tooltip = L("If enabled, Arrange will avoid extrusion calibrate region when placing objects.");
+16
View File
@@ -60,6 +60,22 @@ public:
// using the given camera matrices.
//
void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
//
// ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's
// shadow map. Depth only - the caller masks colour writes and owns the framebuffer.
//
void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position);
//
// ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit.
// intensity == 0, the default, turns the lookup off and restores the plain shading.
//
void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size);
//
// ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
//
// ************************************************************************
+20 -4
View File
@@ -15,7 +15,12 @@ namespace libvgcode {
//| 2--0-------5--7 |
//| \ | | / |
//| 3-------4 |
static constexpr const std::array<uint8_t, 24> VERTEX_DATA = {
// The eight corners the vertex shader knows how to place. Each is sent once and
// referenced by INDEX_DATA below, so the post-transform cache can reuse it across
// the triangles that share it: the shader runs 8 times per segment instead of 24.
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
static constexpr const std::array<uint8_t, 24> INDEX_DATA = {
0, 1, 2, // front spike
0, 2, 3, // front spike
0, 3, 4, // right/bottom body
@@ -31,7 +36,7 @@ void SegmentTemplate::init()
if (m_vao_id != 0)
return;
m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t);
m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t);
int curr_vertex_array;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
@@ -51,12 +56,22 @@ void SegmentTemplate::init()
glsafe(glVertexAttribIPointer(0, 1, GL_UNSIGNED_BYTE, 0, (const void*)0));
#endif // ENABLE_OPENGL_ES
// The element buffer binding is part of the vao state, so it is left bound here
// and restored together with the vao.
glsafe(glGenBuffers(1, &m_ibo_id));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
glsafe(glBindVertexArray(curr_vertex_array));
}
void SegmentTemplate::shutdown()
{
if (m_ibo_id != 0) {
glsafe(glDeleteBuffers(1, &m_ibo_id));
m_ibo_id = 0;
}
if (m_vbo_id != 0) {
glsafe(glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
@@ -71,14 +86,15 @@ void SegmentTemplate::shutdown()
void SegmentTemplate::render(size_t count)
{
if (m_vao_id == 0 || m_vbo_id == 0 || count == 0)
if (m_vao_id == 0 || m_vbo_id == 0 || m_ibo_id == 0 || count == 0)
return;
int curr_vertex_array;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
glsafe(glBindVertexArray(m_vao_id));
glsafe(glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast<GLsizei>(VERTEX_DATA.size()), static_cast<GLsizei>(count)));
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_BYTE,
nullptr, static_cast<GLsizei>(count)));
glsafe(glBindVertexArray(curr_vertex_array));
}
+1
View File
@@ -40,6 +40,7 @@ private:
//
unsigned int m_vao_id{ 0 };
unsigned int m_vbo_id{ 0 };
unsigned int m_ibo_id{ 0 };
//
// Size of the data sent to gpu, in bytes.
//
+62 -6
View File
@@ -16,7 +16,8 @@ static const char* Segments_Vertex_Shader =
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
"const float light_top_specular = 0.6 * 0.125;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
"const float light_top_shininess = 20.0;\n"
"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n"
"const float light_front_diffuse = 0.6 * 0.2;\n"
@@ -30,8 +31,19 @@ static const char* Segments_Vertex_Shader =
"uniform samplerBuffer height_width_angle_tex;\n"
"uniform samplerBuffer color_tex;\n"
"uniform usamplerBuffer segment_index_tex;\n"
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
// world_position, so the caster would write a depth the receiver never looks up.
"uniform float bias_scale;\n"
"in int vertex_id;\n"
"out vec3 color;\n"
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n"
"// single lighting term this replaces, so shading is unchanged while shadows are off.\n"
"out vec3 color_direct;\n"
"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n"
"// needs its world position and, for the depth bias, its eye space normal.\n"
"out vec3 world_position;\n"
"out vec3 shadow_normal;\n"
"vec3 decode_color(float color) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
@@ -40,11 +52,11 @@ static const char* Segments_Vertex_Shader =
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n"
" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n"
" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n"
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"void main() {\n"
" int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
@@ -135,19 +147,63 @@ static const char* Segments_Vertex_Shader =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
" eye_position.z += bias;\n"
" eye_position.z += bias * bias_scale;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
" shadow_normal = eye_normal;\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
static const char* Segments_Fragment_Shader =
"#version 150\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
"uniform float shadow_map_texel;\n"
// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO
// post pass - only takes more light away, so the print reads dimmer and duller than the legend
// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
"uniform float exposure;\n"
"uniform float saturation;\n"
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
"in vec3 color;\n"
"in vec3 color_direct;\n"
"in vec3 world_position;\n"
"in vec3 shadow_normal;\n"
"out vec4 fragment_color;\n"
"float shadow_shade() {\n"
" if (shadow_intensity <= 0.0)\n"
" return 1.0;\n"
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
" vec3 proj = lp.xyz / lp.w;\n"
" proj = proj * 0.5 + 0.5;\n"
" if (proj.z > 1.0)\n"
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
" for (int y = -2; y <= 2; ++y) {\n"
" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n"
" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n"
" }\n"
" }\n"
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
"}\n"
"void main() {\n"
" fragment_color = vec4(color, 1.0);\n"
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
"}\n";
static const char* Options_Vertex_Shader =
+61 -5
View File
@@ -17,7 +17,8 @@ static const char* Segments_Vertex_Shader_ES =
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
"const float light_top_specular = 0.6 * 0.125;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
"const float light_top_shininess = 20.0;\n"
"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n"
"const float light_front_diffuse = 0.6 * 0.3;\n"
@@ -33,6 +34,14 @@ static const char* Segments_Vertex_Shader_ES =
"uniform usampler2D segment_index_tex;\n"
"in float vertex_id_float;\n"
"out vec3 color;\n"
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n"
"// single lighting term this replaces, so shading is unchanged while shadows are off.\n"
"out vec3 color_direct;\n"
"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n"
"// needs its world position and, for the depth bias, its eye space normal.\n"
"out vec3 world_position;\n"
"out vec3 shadow_normal;\n"
"vec3 decode_color(float color) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
@@ -41,11 +50,11 @@ static const char* Segments_Vertex_Shader_ES =
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n"
" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n"
" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n"
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"ivec2 tex_coord(sampler2D sampler, int id) {\n"
" ivec2 tex_size = textureSize(sampler, 0);\n"
@@ -143,17 +152,64 @@ static const char* Segments_Vertex_Shader_ES =
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
" shadow_normal = eye_normal;\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
static const char* Segments_Fragment_Shader_ES =
"#version 300 es\n"
"precision highp float;\n"
"// ORCA: sampler2D defaults to lowp in an ES fragment shader, far too coarse to compare\n"
"// shadow map depths against.\n"
"precision highp sampler2D;\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
"uniform float shadow_map_texel;\n"
// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO
// post pass - only takes more light away, so the print reads dimmer and duller than the legend
// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
"uniform float exposure;\n"
"uniform float saturation;\n"
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
"in vec3 color;\n"
"in vec3 color_direct;\n"
"in vec3 world_position;\n"
"in vec3 shadow_normal;\n"
"out vec4 fragment_color;\n"
"float shadow_shade() {\n"
" if (shadow_intensity <= 0.0)\n"
" return 1.0;\n"
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
" vec3 proj = lp.xyz / lp.w;\n"
" proj = proj * 0.5 + 0.5;\n"
" if (proj.z > 1.0)\n"
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
" for (int y = -2; y <= 2; ++y) {\n"
" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n"
" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n"
" }\n"
" }\n"
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
"}\n"
"void main() {\n"
" fragment_color = vec4(color, 1.0);\n"
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
"}\n";
static const char* Options_Vertex_Shader_ES =
+15
View File
@@ -42,6 +42,21 @@ void Viewer::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
m_impl->render(view_matrix, projection_matrix);
}
void Viewer::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position)
{
m_impl->render_shadow_casters(view_matrix, projection_matrix, light_position);
}
void Viewer::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size)
{
m_impl->set_shadow_map(texture_unit, light_view_projection, intensity, texel_size);
}
void Viewer::set_tone(float exposure, float saturation)
{
m_impl->set_tone(exposure, saturation);
}
EViewType Viewer::get_view_type() const
{
return m_impl->get_view_type();
+109 -4
View File
@@ -763,6 +763,14 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
// ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
m_uni_segments_shadow_intensity_id = glGetUniformLocation(m_segments_shader_id, "shadow_intensity");
m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel");
m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure");
m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation");
m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale");
glcheck();
assert(m_uni_segments_view_matrix_id != -1 &&
m_uni_segments_projection_matrix_id != -1 &&
@@ -875,6 +883,12 @@ void ViewerImpl::reset()
m_travels_time = { 0.0f, 0.0f };
m_vertices.clear();
m_vertices_colors.clear();
// swap rather than clear: these are sized by the print, and a reset means the memory
// should go back, not sit reserved until the next load
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().swap(m_colors_scratch);
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
@@ -1048,6 +1062,37 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id));
}
// Index of the first vertex of each layer, walked back to front so that a layer with no
// vertex of its own inherits the next layer's index and the array stays non-decreasing.
if (!m_layers.empty()) {
const uint32_t vertices_count = static_cast<uint32_t>(m_vertices.size());
m_layer_first_vertex.assign(m_layers.count(), vertices_count);
for (uint32_t i = vertices_count; i > 0; --i) {
const uint32_t layer_id = m_vertices[i - 1].layer_id;
if (layer_id < m_layer_first_vertex.size())
m_layer_first_vertex[layer_id] = i - 1;
}
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
// the running time at each layer's first vertex, summed in vertex order so that
// get_estimated_time_at() matches a full accumulation exactly
std::array<float, TIME_MODES_COUNT> running{};
for (std::vector<float>& times : m_layer_start_times)
times.assign(m_layer_first_vertex.size(), 0.0f);
size_t layer = 0;
for (size_t i = 0; i <= m_vertices.size(); ++i) {
for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
m_layer_start_times[j][layer] = running[j];
}
if (i < m_vertices.size()) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
running[j] += m_vertices[i].times[j];
}
}
}
if (!m_layers.empty())
m_layers.set_view_range(0, static_cast<uint32_t>(m_layers.count()) - 1);
@@ -1261,7 +1306,10 @@ void ViewerImpl::update_colors_texture()
// Based on current settings and slider position, we might want to render some
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
std::vector<float> colors(m_vertices_colors.size());
// Reused across calls: this runs on every slider tick, and the allocation alone is
// 4 bytes per vertex of the whole print each time.
std::vector<float>& colors = m_colors_scratch;
colors.resize(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
@@ -1321,7 +1369,7 @@ void ViewerImpl::update_colors()
m_settings.update_colors = false;
}
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
void ViewerImpl::apply_pending_updates()
{
if (m_settings.update_view_full_range)
update_view_full_range();
@@ -1331,6 +1379,11 @@ void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matr
if (m_settings.update_colors)
update_colors();
}
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
apply_pending_updates();
const Mat4x4 inv_view_matrix = inverse(view_matrix);
const Vec3 camera_position = { inv_view_matrix[12], inv_view_matrix[13], inv_view_matrix[14] };
@@ -1345,6 +1398,30 @@ void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matr
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
}
void ViewerImpl::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position)
{
apply_pending_updates();
// Only the extrusions and travels cast: the option markers are indicators, not material.
m_rendering_shadow_casters = true;
render_segments(view_matrix, projection_matrix, light_position);
m_rendering_shadow_casters = false;
}
void ViewerImpl::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size)
{
m_shadow_map_texture_unit = texture_unit;
m_shadow_light_vp = light_view_projection;
m_shadow_intensity = intensity;
m_shadow_map_texel = texel_size;
}
void ViewerImpl::set_tone(float exposure, float saturation)
{
m_exposure = exposure;
m_saturation = saturation;
}
void ViewerImpl::set_view_type(EViewType type)
{
m_settings.view_type = type;
@@ -1516,8 +1593,19 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
float ViewerImpl::get_estimated_time_at(size_t id) const
{
return std::accumulate(m_vertices.begin(), m_vertices.begin() + id + 1, 0.0f,
[this](float a, const PathVertex& v) { return a + v.times[static_cast<size_t>(m_settings.time_mode)]; });
const size_t mode = static_cast<size_t>(m_settings.time_mode);
if (mode >= TIME_MODES_COUNT || id >= m_vertices.size())
return 0.0f;
size_t first = 0;
float time = 0.0f;
const size_t layer = static_cast<size_t>(m_vertices[id].layer_id);
if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) {
first = m_layer_first_vertex[layer];
time = m_layer_start_times[mode][layer];
}
for (size_t i = first; i <= id; ++i)
time += m_vertices[i].times[mode];
return time;
}
Color ViewerImpl::get_vertex_color(const PathVertex& v) const
@@ -1722,6 +1810,10 @@ size_t ViewerImpl::get_used_cpu_memory() const
ret += sizeof(m_extrusion_roles_colors);
ret += sizeof(m_options_colors);
ret += STDVEC_MEMSIZE(m_vertices, PathVertex);
for (const std::vector<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
ret += m_valid_lines_bitset.size_in_bytes_cpu();
ret += m_height_range.size_in_bytes_cpu();
ret += m_width_range.size_in_bytes_cpu();
@@ -1787,7 +1879,11 @@ void ViewerImpl::update_view_full_range()
const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)];
const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)];
// every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop
// below would skip all of them anyway
auto first_it = m_vertices.begin();
if (layers_range[0] < m_layer_first_vertex.size())
first_it += m_layer_first_vertex[layers_range[0]];
while (first_it != m_vertices.end() &&
(first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) {
++first_it;
@@ -1994,6 +2090,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off.
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity));
glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f));
glsafe(glDisable(GL_CULL_FACE));
+60
View File
@@ -71,6 +71,24 @@ public:
// Render the toolpaths
//
void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
//
// ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's
// shadow map. Only depth matters here, so the caller masks colour writes; light_position
// takes the place of the camera when the segment boxes are expanded, which gives their
// silhouette as the light sees it.
//
void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position);
//
// ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit.
// intensity == 0, the default, turns the lookup off and restores the plain shading.
//
void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size);
//
// ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
EViewType get_view_type() const { return m_settings.view_type; }
void set_view_type(EViewType type);
@@ -234,6 +252,20 @@ private:
//
std::array<float, TIME_MODES_COUNT> m_total_time{ 0.0f, 0.0f };
//
// Running sum of the vertex estimated times at each layer's first vertex, for each time mode,
// so that get_estimated_time_at() only accumulates the vertices of one layer.
//
std::array<std::vector<float>, TIME_MODES_COUNT> m_layer_start_times;
//
// For each layer L, the index of the first vertex whose layer_id is >= L (m_vertices.size()
// if there is none). Derived from the vertices, so it stays exact whatever order they arrive in.
//
std::vector<uint32_t> m_layer_first_vertex;
//
// Scratch buffer for update_colors_texture(), kept alive across slider steps
//
std::vector<float> m_colors_scratch;
//
// Detected travel moves times
//
std::array<float, TIME_MODES_COUNT> m_travels_time{ 0.0f, 0.0f };
@@ -330,6 +362,13 @@ private:
int m_uni_segments_height_width_angle_tex_id{ -1 };
int m_uni_segments_colors_tex_id{ -1 };
int m_uni_segments_segment_index_tex_id{ -1 };
int m_uni_segments_shadow_map_id{ -1 };
int m_uni_segments_shadow_light_vp_id{ -1 };
int m_uni_segments_shadow_intensity_id{ -1 };
int m_uni_segments_shadow_map_texel_id{ -1 };
int m_uni_segments_exposure_id{ -1 };
int m_uni_segments_saturation_id{ -1 };
int m_uni_segments_bias_scale_id{ -1 };
//
// Caches for OpenGL uniforms id for options shader
//
@@ -469,6 +508,27 @@ private:
size_t m_enabled_options_tex_size{ 0 };
#endif // ENABLE_OPENGL_ES
//
// ORCA: realistic view. Shadow map state set by set_shadow_map(), consumed by the segments
// shader. m_rendering_shadow_casters forces the intensity to 0 for the depth pass, which
// must not sample the very map it is writing.
//
// Defaults past the four texture units render_segments() binds itself, so the sampler never
// aliases one of the buffer textures before the owner of the map has said where it lives.
int m_shadow_map_texture_unit{ 4 };
Mat4x4 m_shadow_light_vp{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
float m_shadow_intensity{ 0.0f };
float m_shadow_map_texel{ 0.0f };
bool m_rendering_shadow_casters{ false };
//
// ORCA: realistic view. Tone set by set_tone(), consumed by the segments shader.
// The identity values leave the shading as it is outside realistic view.
//
float m_exposure{ 1.0f };
float m_saturation{ 1.0f };
void apply_pending_updates();
void update_view_full_range();
void update_color_ranges();
void update_heights_widths();
+27 -14
View File
@@ -12,6 +12,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Time.hpp"
@@ -385,7 +386,10 @@ namespace Slic3r
obj->bind_state = "free";
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->m_is_online = true;
// Route through set_online_state() (rather than writing m_is_online directly) so the
// DeviceOnline lifecycle event fires consistently; same effective value/behavior
// here since the object was already online in the common case.
obj->set_online_state(true);
obj->set_dev_name(dev_name);
/* if (!obj->dev_ip.empty()) {
Slic3r::GUI::wxGetApp().app_config->set_str("ip_address", obj->dev_id, obj->dev_ip);
@@ -403,6 +407,10 @@ namespace Slic3r
obj->bind_sec_link = sec_link;
obj->dev_connection_name = connection_name;
obj->bind_ssdp_version = ssdp_version;
// Discovery establishes the initial reachability state. Do not report it as an
// online transition; DeviceDiscovered below is the lifecycle event for a new
// device. Subsequent updates route through set_online_state(), so a known device
// still emits DeviceOnline/DeviceOffline when its reachability actually changes.
obj->m_is_online = true;
//load access code
@@ -418,6 +426,15 @@ namespace Slic3r
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " New Machine, dev_id= " << dev_id
<< ", ip = " << dev_ip <<", printer_name = " << dev_name
<< ", con_type= " << connect_type <<", signal= " << printer_signal << ", bind_state= " << bind_state;
// First discovery of a genuinely new device (not a periodic SSDP/heartbeat update to
// an already-known one, which is handled in the branch above).
{
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
}
}
update_local_machine(*obj, config);
}
@@ -658,12 +675,7 @@ namespace Slic3r
m_agent->disconnect_printer();
it->second->reset();
#if !BBL_RELEASE_TO_PUBLIC
AppConfig* config = get_app_config();
it->second->connect(config && config->get("enable_ssl_for_mqtt") == "true");
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->connect();
it->second->set_lan_mode_connection_state(true);
}
}
@@ -685,12 +697,7 @@ namespace Slic3r
{
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id;
it->second->reset();
#if !BBL_RELEASE_TO_PUBLIC
AppConfig* config = get_app_config();
it->second->connect(config && config->get("enable_ssl_for_mqtt") == "true");
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->connect();
it->second->set_lan_mode_connection_state(true);
}
}
@@ -1077,8 +1084,14 @@ namespace Slic3r
}
void DeviceManager::OnSelectedMachineChanged(const std::string& /*pre_dev_id*/,
const std::string& /*new_dev_id*/)
const std::string& new_dev_id)
{
{
LifecycleEventContext ctx;
ctx.name = new_dev_id; // empty string is a valid deselection
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceSelected, ctx);
}
if (MachineObject* obj_ = get_selected_machine()) {
GUI::wxGetApp().sidebar().update_sync_status(obj_);
if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription)
+76 -35
View File
@@ -4,7 +4,9 @@
#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"
#include "GuiColor.hpp"
@@ -1790,20 +1792,10 @@ 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)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
}
int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id)
{
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);
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_start_camera()
@@ -2613,7 +2605,15 @@ void MachineObject::reset()
void MachineObject::set_print_state(std::string status)
{
const bool changed = (print_status != status);
print_status = status;
if (changed) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = print_status;
fire_lifecycle_event(LifecycleEvent::PrintStateChanged, ctx);
}
}
// why: printer agents can report progress without BBL cloud task identity.
@@ -2635,15 +2635,44 @@ void MachineObject::update_print_progress(const json& value)
curr_task->task_progress = mc_print_percent;
}
int MachineObject::connect(bool use_openssl)
int MachineObject::connect()
{
if (get_dev_ip().empty()) return -1;
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 (...) {
;
}
@@ -2654,7 +2683,10 @@ int MachineObject::connect(bool use_openssl)
int MachineObject::disconnect()
{
if (m_agent) {
return m_agent->disconnect_printer();
const int result = m_agent->disconnect_printer();
if (result == 0)
set_online_state(false);
return result;
}
return -1;
}
@@ -2684,8 +2716,16 @@ bool MachineObject::is_connecting()
void MachineObject::set_online_state(bool on_off)
{
const bool changed = (m_is_online != on_off);
m_is_online = on_off;
if (!on_off) m_active_state = NotActive;
if (changed) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = on_off ? "online" : "offline";
fire_lifecycle_event(on_off ? LifecycleEvent::DeviceOnline : LifecycleEvent::DeviceOffline, ctx);
}
}
bool MachineObject::is_info_ready(bool check_version) const
@@ -2821,13 +2861,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
parse_msg_count++;
std::chrono::system_clock::time_point clock_start = std::chrono::system_clock::now();
this->set_online_state(true);
std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now();
auto diff1 = std::chrono::duration_cast<std::chrono::microseconds>(curr_time - last_update_time);
/* update last received time */
last_update_time = std::chrono::system_clock::now();
json j_pre;
bool parse_ok = false;
@@ -2840,8 +2873,29 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
/* post process payload */
sanitizeToUtf8(payload);
BOOST_LOG_TRIVIAL(info) << "parse_json: sanitize to utf8";
try {
j_pre = json::parse(payload);
parse_ok = true;
}
catch (...) {}
}
bool client_disconnected = false;
if (parse_ok && j_pre.is_object() && j_pre.contains("event") && j_pre["event"].is_object() &&
j_pre["event"].contains("event") && j_pre["event"]["event"].is_string()) {
client_disconnected = j_pre["event"]["event"].get<std::string>() == "client.disconnected";
}
// A disconnect notification is a transport message too, but it must not first mark an
// already-offline device as online through the generic message-received path.
set_online_state(!client_disconnected);
std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now();
auto diff1 = std::chrono::duration_cast<std::chrono::microseconds>(curr_time - last_update_time);
/* update last received time */
last_update_time = std::chrono::system_clock::now();
try {
bool restored_json = false;
// A frame is authoritative for removals only when it is a full snapshot
@@ -4683,19 +4737,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
}
}
// event info
try {
if (j.contains("event")) {
if (j["event"].contains("event")) {
if (j["event"]["event"].get<std::string>() == "client.disconnected")
set_online_state(false);
else if (j["event"]["event"].get<std::string>() == "client.connected")
set_online_state(true);
}
}
}
catch (...) {}
if (!key_field_only) {
BOOST_LOG_TRIVIAL(trace) << "parse_json m_active_state =" << m_active_state;
parse_state_changed_event();
+3 -3
View File
@@ -685,7 +685,7 @@ public:
/* machine mqtt apis */
int connect(bool use_openssl = true);
int connect();
int disconnect();
json_diff print_json;
@@ -803,8 +803,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 = "");
@@ -890,6 +889,7 @@ public:
bool is_connected();
bool is_connecting();
// Emits DeviceOnline or DeviceOffline only when the reachability state changes.
void set_online_state(bool on_off);
bool is_online() { return m_is_online; }
bool is_info_ready(bool check_version = true) const;
+136 -28
View File
@@ -104,6 +104,31 @@ static std::string get_view_type_string(libvgcode::EViewType view_type)
return "";
}
// ORCA: Stable, locale independent names used to persist a view type in the application config.
// Keep these in sync with the entries of libvgcode::EViewType exposed in the preview combo box.
static const std::vector<std::pair<std::string, libvgcode::EViewType>>& view_type_config_map()
{
static const std::vector<std::pair<std::string, libvgcode::EViewType>> map = {
{ "summary", libvgcode::EViewType::Summary },
{ "feature_type", libvgcode::EViewType::FeatureType },
{ "color_print", libvgcode::EViewType::ColorPrint },
{ "speed", libvgcode::EViewType::Speed },
{ "actual_speed", libvgcode::EViewType::ActualSpeed },
{ "acceleration", libvgcode::EViewType::Acceleration },
{ "jerk", libvgcode::EViewType::Jerk },
{ "height", libvgcode::EViewType::Height },
{ "width", libvgcode::EViewType::Width },
{ "volumetric_flow_rate", libvgcode::EViewType::VolumetricFlowRate },
{ "actual_volumetric_flow_rate", libvgcode::EViewType::ActualVolumetricFlowRate },
{ "layer_time_linear", libvgcode::EViewType::LayerTimeLinear },
{ "layer_time_logarithmic", libvgcode::EViewType::LayerTimeLogarithmic },
{ "fan_speed", libvgcode::EViewType::FanSpeed },
{ "temperature", libvgcode::EViewType::Temperature },
{ "pressure_advance", libvgcode::EViewType::PressureAdvance },
};
return map;
}
// Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
// Returns -1 if there is no such member.
static int find_close_layer_idx(const std::vector<double> &zs, double &z, double eps)
@@ -1091,9 +1116,7 @@ void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle)
// Default view type at first slice.
// May be overridden in load() once we know how many tools are actually used in the G-code.
m_nozzle_nums = preset_bundle ? preset_bundle->get_printer_extruder_count() : 1;
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType);
m_view_type_sel = (it != view_type_items.end()) ? std::distance(view_type_items.begin(), it) : 0;
set_view_type(libvgcode::EViewType::FeatureType);
apply_default_view_type();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished");
}
@@ -1114,6 +1137,73 @@ void GCodeViewer::set_scale(float scale)
}
}
// ORCA: Preview default view type preference, see "preview_default_view_type" in the application config.
std::string GCodeViewer::view_type_to_config_name(libvgcode::EViewType type)
{
for (const auto& [name, value] : view_type_config_map()) {
if (value == type)
return name;
}
return std::string();
}
bool GCodeViewer::view_type_from_config_name(const std::string& name, libvgcode::EViewType& type)
{
for (const auto& [config_name, value] : view_type_config_map()) {
if (config_name == name) {
type = value;
return true;
}
}
return false;
}
std::vector<std::pair<std::string, std::string>> GCodeViewer::default_view_type_choices()
{
std::vector<std::pair<std::string, std::string>> choices = {
{ "auto", _u8L("Automatic") },
{ "last", _u8L("Last used") },
};
for (const auto& [name, type] : view_type_config_map())
choices.push_back({ name, get_view_type_string(type) });
return choices;
}
void GCodeViewer::select_view_type(libvgcode::EViewType type)
{
auto it = std::find(view_type_items.begin(), view_type_items.end(), type);
m_view_type_sel = (it != view_type_items.end()) ? static_cast<int>(std::distance(view_type_items.begin(), it)) : 0;
set_view_type(type);
}
// ORCA: Pick the view type the preview opens with, following the "preview_default_view_type" preference:
// a fixed view type, the one the user picked last ("last"), or the automatic choice ("auto", the default)
// which shows Filament for multi material prints and Line Type for single material ones.
// The default is only (re)applied when it actually changes, so a view type picked by hand survives a reslice.
void GCodeViewer::apply_default_view_type()
{
const std::string preference = wxGetApp().app_config->get("preview_default_view_type");
std::string key = preference;
libvgcode::EViewType type = libvgcode::EViewType::FeatureType;
if (preference == "last") {
if (!view_type_from_config_name(wxGetApp().app_config->get("preview_last_view_type"), type))
type = libvgcode::EViewType::FeatureType;
}
else if (!view_type_from_config_name(preference, type)) {
// "auto", or an unknown value written by a newer version
const bool multi_material = m_viewer.get_used_extruders_count() > 1;
type = multi_material ? libvgcode::EViewType::ColorPrint : libvgcode::EViewType::FeatureType;
key = multi_material ? "auto_multi_material" : "auto_single_material";
}
if (m_applied_default_view_type_key == key)
return;
m_applied_default_view_type_key = key;
select_view_type(type);
}
void GCodeViewer::update_by_mode(ConfigOptionMode mode)
{
view_type_items.clear();
@@ -1395,27 +1485,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
// load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
// ORCA: Apply smart default view type when extruder count changes.
// Multi-color: ColorPrint (Filament), Single-color: FeatureType (Line Type).
// User selections persist within same extruder count, defaults reapply on count change.
int current_count = m_viewer.get_used_extruders_count();
if (current_count > 1) {
if (m_last_extruder_count_default_applied != 2) {
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::ColorPrint);
if (it != view_type_items.end())
m_view_type_sel = std::distance(view_type_items.begin(), it);
set_view_type(libvgcode::EViewType::ColorPrint);
m_last_extruder_count_default_applied = 2;
}
} else {
if (m_last_extruder_count_default_applied != 1) {
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType);
if (it != view_type_items.end())
m_view_type_sel = std::distance(view_type_items.begin(), it);
set_view_type(libvgcode::EViewType::FeatureType);
m_last_extruder_count_default_applied = 1;
}
}
// ORCA: Apply the default view type now that we know how many tools the G-code actually uses.
apply_default_view_type();
// BBS: data for rendering color arrangement recommendation
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
@@ -1627,7 +1698,7 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height);
if (m_viewer.get_extrusion_roles().empty())
if (m_viewer.get_extrusion_roles_count() == 0)
return;
render_toolpaths();
@@ -1641,6 +1712,29 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type());
}
void GCodeViewer::render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position)
{
if (!has_data())
return;
m_viewer.render_shadow_casters(
libvgcode::convert(static_cast<Matrix4f>(light_view_matrix.matrix().cast<float>())),
libvgcode::convert(static_cast<Matrix4f>(light_projection_matrix.matrix().cast<float>())),
libvgcode::convert(static_cast<Vec3f>(light_position.cast<float>())));
}
void GCodeViewer::set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size)
{
m_viewer.set_shadow_map(texture_unit,
libvgcode::convert(static_cast<Matrix4f>(light_view_projection.matrix().cast<float>())),
intensity, texel_size);
}
void GCodeViewer::set_tone(float exposure, float saturation)
{
m_viewer.set_tone(exposure, saturation);
}
void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin)
{
if (m_viewer.get_extrusion_roles().empty())
@@ -3426,6 +3520,12 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret;
ret.reserve(custom_gcode_per_print_z.size());
// Loop invariant, but built lazily: this lambda runs once per extruder on every frame
// and most prints reach neither colour change below, so fetching it up front would cost
// more than the per-item fetch it replaces.
std::vector<float> zs;
bool zs_built = false;
for (const auto& item : custom_gcode_per_print_z) {
if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
continue;
@@ -3433,7 +3533,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
if (item.type != ColorChange)
continue;
const std::vector<float> zs = m_viewer.get_layers_zs();
if (!zs_built) {
zs = m_viewer.get_layers_zs();
zs_built = true;
}
auto lower_b = std::lower_bound(zs.begin(), zs.end(),
static_cast<float>(item.print_z - epsilon()));
if (lower_b == zs.end())
@@ -3558,6 +3661,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
m_view_type_sel = i;
set_view_type(view_type_items[m_view_type_sel]);
reset_visible(view_type_items[m_view_type_sel]);
// ORCA: remember the pick so the "Last used" preview default can restore it
const std::string view_type_name = view_type_to_config_name(view_type_items[m_view_type_sel]);
if (!view_type_name.empty())
wxGetApp().app_config->set("preview_last_view_type", view_type_name);
update_moves_slider();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
@@ -4582,6 +4689,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
// ORCA: Get layer Zs as doubles
std::vector<double> layer_zs = get_layers_zs();
// loop invariant, same reason as the layer Zs above
const std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) {
ImGui::Dummy({window_padding, window_padding});
@@ -4601,7 +4710,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
imgui.text(buf);
ImGui::SameLine(max_len * 1.5);
std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
float custom_gcode_time = 0;
if (layer > 0)
{
@@ -4650,7 +4758,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::string print_str = _u8L("Model printing time");
std::string total_str = _u8L("Total time");
float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x;
if (m_viewer.get_layers_estimated_times().empty())
if (m_viewer.get_layers_count() == 0)
max_len += ImGui::CalcTextSize(total_str.c_str()).x;
else {
if (m_viewer.get_view_type() == libvgcode::EViewType::FeatureType)
+19 -1
View File
@@ -228,7 +228,8 @@ private:
std::vector<libvgcode::EViewType> view_type_items;
std::vector<std::string> view_type_items_str;
int m_view_type_sel = 0;
int m_last_extruder_count_default_applied{0}; // 0=unset, 1=single, 2+=multi
// ORCA: which default view type was last applied, see apply_default_view_type(). Empty until the first one is applied.
std::string m_applied_default_view_type_key;
std::vector<EMoveType> options_items;
bool m_legend_visible{ true };
@@ -279,6 +280,13 @@ public:
void render_scene(int canvas_width, int canvas_height);
// Legend, sliders, the marker's position window and the G-code window, all ImGui.
void render_overlay(int canvas_width, int canvas_height, int right_margin);
// ORCA: realistic view. Depth-only pass drawing the toolpaths as the light sees them, into
// the shadow map the caller has bound, and the map they sample back in render_scene.
void render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position);
void set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size);
// ORCA: tone applied to the shaded toolpaths, paying back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0 is a no-op.
void set_tone(float exposure, float saturation);
//BBS
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
@@ -324,6 +332,16 @@ public:
void set_view_type(libvgcode::EViewType type) {
m_viewer.set_view_type(type);
}
// ORCA: select a view type in the preview combo box and apply it
void select_view_type(libvgcode::EViewType type);
// ORCA: apply the "preview_default_view_type" preference, see the definition for the supported values
void apply_default_view_type();
// ORCA: stable, locale independent name of a view type, as stored in the application config
static std::string view_type_to_config_name(libvgcode::EViewType type);
static bool view_type_from_config_name(const std::string& name, libvgcode::EViewType& type);
// ORCA: (config value, translated label) pairs for the "preview_default_view_type" preference combo box
static std::vector<std::pair<std::string, std::string>> default_view_type_choices();
void reset_visible(libvgcode::EViewType type) {
if (type == libvgcode::EViewType::FeatureType) {
auto roles = m_viewer.get_extrusion_roles();
+125 -112
View File
@@ -43,6 +43,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <slic3r/GUI/GUI_Utils.hpp>
@@ -2199,8 +2200,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// Recorded by PartPlate::render_icons() below, when it runs.
wxGetApp().plater()->get_partplate_list().clear_hover_tooltip();
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
// Invalidate the shadow map each frame; the View3D and Preview paths below rebuild it. This
// keeps the Assemble canvas from sampling a stale map with an outdated light matrix.
m_shadow_map_valid = false;
_render_background();
@@ -2251,6 +2252,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_selection();
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
// Realistic view: the print casts a shadow onto the plate here as it does in View3D.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
}
@@ -5026,8 +5029,16 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
//BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
if (object_moved)
if (object_moved) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "moved";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
}
// BBS: support wipe-tower for multi-plates
for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) {
@@ -5148,8 +5159,16 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
//BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
if (!done.empty())
if (!done.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "rotated";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
}
m_dirty = true;
}
@@ -5240,8 +5259,16 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
//BBS: notify object info update
wxGetApp().plater()->show_object_info();
if (!done.empty())
if (!done.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "scaled";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
}
m_dirty = true;
}
@@ -7644,11 +7671,20 @@ bool GLCanvas3D::_is_fxaa_enabled() const
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED);
}
bool GLCanvas3D::_is_realistic_view_enabled() const
{
const AppConfig* cfg = wxGetApp().app_config;
if (cfg == nullptr || !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE))
return false;
// Prepare and Assemble follow the umbrella toggle alone; Preview needs its own opt-in.
return m_canvas_type != ECanvasType::CanvasPreview || cfg->get_bool(SETTING_OPENGL_REALISTIC_PREVIEW);
}
bool GLCanvas3D::_is_ssao_enabled() const
{
if (wxGetApp().app_config == nullptr)
return false;
return wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE) &&
return _is_realistic_view_enabled() &&
wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO);
}
@@ -7803,95 +7839,23 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height)
const Camera& camera = wxGetApp().plater()->get_camera();
GLint prev_stencil_mask = 0xFF;
glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask));
GLboolean prev_stencil_test = GL_FALSE;
glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test));
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
// Build stencil mask for bed/plate and apply SSAO only outside this mask.
glsafe(::glEnable(GL_STENCIL_TEST));
glsafe(::glStencilMask(0xFF));
glsafe(::glClearStencil(0));
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE));
// Mark only visible plate pixels (do not exclude objects in front of plate).
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glDepthFunc(GL_LEQUAL));
GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) {
GLShaderProgram* flat = wxGetApp().get_shader("flat");
if (flat != nullptr) {
flat->start_using();
flat->set_uniform("projection_matrix", camera.get_projection_matrix());
GLModel plate_mask;
GLModel::Geometry mask;
mask.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
if (build_volume.type() == BuildVolume_Type::Rectangle) {
const BoundingBox3Base<Vec3d> bb = build_volume.bounding_volume();
mask.reserve_vertices(4);
mask.reserve_indices(6);
mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.min.y(), 0.0f));
mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.min.y(), 0.0f));
mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.max.y(), 0.0f));
mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.max.y(), 0.0f));
mask.add_triangle(0, 1, 2);
mask.add_triangle(0, 2, 3);
} else if (build_volume.type() == BuildVolume_Type::Circle) {
const Vec2f c = Vec2f(unscaled<float>(build_volume.circle().center.x()), unscaled<float>(build_volume.circle().center.y()));
const float r = unscaled<float>(build_volume.circle().radius);
const int segments = 64;
mask.reserve_vertices(segments + 1);
mask.reserve_indices(segments * 3);
mask.add_vertex(Vec3f(c.x(), c.y(), 0.0f));
for (int i = 0; i < segments; ++i) {
const float a = (2.0f * float(PI) * float(i)) / float(segments);
mask.add_vertex(Vec3f(c.x() + r * std::cos(a), c.y() + r * std::sin(a), 0.0f));
}
for (int i = 0; i < segments; ++i) {
const unsigned int i1 = 1 + i;
const unsigned int i2 = 1 + ((i + 1) % segments);
mask.add_triangle(0, i1, i2);
}
}
if (mask.vertices_count() > 0 && mask.indices_count() > 0) {
plate_mask.init_from(std::move(mask));
flat->set_uniform("view_model_matrix", camera.get_view_matrix());
plate_mask.render(flat);
}
flat->stop_using();
}
}
glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glStencilMask(0x00));
glsafe(::glStencilFunc(GL_NOTEQUAL, 1, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
shader->start_using();
shader->set_uniform("view_model_matrix", Transform3d::Identity());
shader->set_uniform("projection_matrix", Transform3d::Identity());
shader->set_uniform("color_texture", 0);
shader->set_uniform("depth_texture", 1);
shader->set_uniform("inv_tex_size", Vec2f(1.0f / static_cast<float>(width), 1.0f / static_cast<float>(height)));
shader->set_uniform("z_near", camera.get_near_z());
shader->set_uniform("z_far", camera.get_far_z());
// The shader reconstructs the surface normal from the depth buffer, there being no normal
// target to read: it unprojects a pixel back into view space, then measures the result
// against world +Z expressed in view space to tell a top surface from a wall.
const Matrix4d inv_projection_matrix = camera.get_projection_matrix().matrix().inverse();
shader->set_uniform("inv_projection_matrix", inv_projection_matrix);
const Vec3d up_view = (camera.get_view_matrix().matrix().block<3, 3>(0, 0) * Vec3d::UnitZ()).normalized();
shader->set_uniform("up_view", up_view);
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id));
@@ -7903,13 +7867,6 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height)
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
shader->stop_using();
if (!prev_stencil_test)
glsafe(::glDisable(GL_STENCIL_TEST));
glsafe(::glStencilMask(prev_stencil_mask));
glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
glsafe(::glDepthMask(prev_depth_mask));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
@@ -8154,15 +8111,19 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
{
if (wxGetApp().app_config == nullptr)
return;
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE))
if (!_is_realistic_view_enabled())
return;
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS))
return;
if (m_volumes.empty())
return;
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
// The preview canvas holds no volumes of its own for FFF. Once slicing has run its printed
// geometry is the G-code toolpaths, which both cast into the map here and sample it back in
// _render_gcode; before slicing there are only shells, and nothing casts at all. View3D and
// SLA preview use m_volumes. The shells are deliberately never casters: they are a
// translucent ghost of the whole object, so they would drop the solid shadow of a print that
// has not been sliced, and at any layer below the last, one that is not there yet.
const bool toolpath_casters = m_canvas_type == ECanvasType::CanvasPreview && m_gcode_viewer.has_data();
if (!toolpath_casters && m_volumes.empty())
return;
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
@@ -8174,10 +8135,30 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
// Bounding box of the printable objects (the shadow casters).
BoundingBoxf3 obj_bb;
for (const GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
obj_bb.merge(volume->transformed_bounding_box());
if (toolpath_casters) {
// Merged corner by corner: BoundingBoxf3(min, max) marks itself undefined at zero
// Z extent, which a single layer print gives, and the check below would then drop
// every shadow in the frame.
const BoundingBoxf3& paths_bb = m_gcode_viewer.get_paths_bounding_box();
if ((paths_bb.min.array() <= paths_bb.max.array()).all()) {
obj_bb.merge(paths_bb.min);
obj_bb.merge(paths_bb.max);
}
// Only the enabled layers are drawn, so fitting the map to the whole print wastes
// its depth range and makes contact shadows shift as the slider moves. The z = 0
// shadow is enclosed separately below, so the plate shadow is unaffected.
const std::vector<double> layer_zs = m_gcode_viewer.get_layers_zs();
if (!layer_zs.empty()) {
const size_t top = std::min<size_t>(m_gcode_viewer.get_layers_z_range()[1], layer_zs.size() - 1);
obj_bb.max.z() = std::max(obj_bb.min.z(), std::min(obj_bb.max.z(), layer_zs[top]));
}
}
else {
for (const GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
obj_bb.merge(volume->transformed_bounding_box());
}
}
if (!obj_bb.defined)
return; // no objects to cast shadows
@@ -8299,16 +8280,21 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
glsafe(::glPolygonOffset(4.0f, 4.0f));
glsafe(::glDisable(GL_CULL_FACE));
shader->start_using();
shader->set_uniform("projection_matrix", Transform3d(light_proj));
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
shader->set_uniform("view_model_matrix", view_model);
volume->model.render(shader);
if (toolpath_casters)
m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
// Only this branch draws through "flat"; the toolpaths bring their own program.
else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
shader->start_using();
shader->set_uniform("projection_matrix", Transform3d(light_proj));
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
shader->set_uniform("view_model_matrix", view_model);
volume->model.render(shader);
}
shader->stop_using();
}
shader->stop_using();
// Restore state
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
@@ -8517,7 +8503,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return;
}
const bool realistic_mode = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE);
const bool realistic_mode = _is_realistic_view_enabled();
const bool realistic_phong = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_PHONG);
const std::string shader_name = (realistic_mode && realistic_phong) ? "phong" : "gouraud";
GLShaderProgram* shader = wxGetApp().get_shader(shader_name);
@@ -8741,7 +8727,34 @@ void GLCanvas3D::_render_wireframe_overlay()
//BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{
// Realistic view: the toolpaths receive the same depth map they were rendered into by
// _render_shadows, which is what gives them object-on-object and self shadows. Intensity 0
// short-circuits the lookup in the shader, so this is inert whenever the map is missing.
const bool receive_shadows = m_shadow_map_valid && m_shadow_map_texture_id != 0 && m_shadow_map_size != 0;
if (receive_shadows) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glActiveTexture(GL_TEXTURE0));
m_gcode_viewer.set_shadow_map(4, m_shadow_light_vp, 0.35f, 1.0f / static_cast<float>(m_shadow_map_size));
}
else
m_gcode_viewer.set_shadow_map(4, Transform3d::Identity(), 0.0f, 0.0f);
// The lighting term leaves the print dimmer and duller than the legend colours. Saturation
// pays back the duller half in both modes; brightness only where something takes light off
// again - realistic view with at least one lossy pass on - else the lift would just clip.
const AppConfig* cfg = wxGetApp().app_config;
const bool lossy_passes = cfg != nullptr && _is_realistic_view_enabled() &&
(cfg->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
m_gcode_viewer.set_tone(lossy_passes ? 1.1f : 1.0f, 1.15f);
m_gcode_viewer.render_scene(canvas_width, canvas_height);
if (receive_shadows) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
}
}
void GLCanvas3D::_render_gcode_overlay(int canvas_width, int canvas_height)
@@ -9831,7 +9844,7 @@ void GLCanvas3D::_render_canvas_toolbar()
);
create_menu_item( _utf8(L("Realistic View")),
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
true, // work on all
cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE),
[&cfg]{
cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE));
+1
View File
@@ -1324,6 +1324,7 @@ private:
void _picking_pass();
void _rectangular_selection_picking_pass();
bool _is_fxaa_enabled() const;
bool _is_realistic_view_enabled() const;
bool _is_ssao_enabled() const;
int _get_effective_fps_cap() const;
bool _is_fps_overlay_enabled() const;
+8
View File
@@ -2179,6 +2179,8 @@ void GUI_App::init_networking_callbacks()
obj->command_get_access_code();
if (m_agent)
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
obj->set_online_state(true);
}
});
});
@@ -2217,6 +2219,8 @@ void GUI_App::init_networking_callbacks()
obj->command_get_version();
event.SetInt(0);
event.SetString(obj->get_dev_id());
obj->set_online_state(true);
} else if (state == ConnectStatus::ConnectStatusFailed) {
// Orca: only update status if same device id
if (m_device_manager->selected_machine != dev_id) return;
@@ -2232,10 +2236,14 @@ void GUI_App::init_networking_callbacks()
wxGetApp().show_dialog(text);
}
event.SetInt(-1);
obj->set_online_state(false);
} else if (state == ConnectStatus::ConnectStatusLost) {
m_device_manager->set_selected_machine("");
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost";
obj->set_online_state(false);
} else {
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state;
+39 -1
View File
@@ -1,5 +1,6 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Factories.hpp"
//#include "GUI_ObjectLayers.hpp"
@@ -10,6 +11,7 @@
#include "BitmapComboBox.hpp"
#include "MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "OptionsGroup.hpp"
#include "Tab.hpp"
@@ -1159,17 +1161,39 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
if (m_objects_model->GetItemType(item) & itObject) {
std::string name = m_objects_model->GetName(item).ToUTF8().data();
if (obj->name != name) {
const std::string previous_name = obj->name;
obj->name = name;
// if object has just one volume, rename this volume too
if (obj->volumes.size() == 1)
obj->volumes[0]->name = obj->name;
Slic3r::save_object_mesh(*obj);
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.id = std::to_string(obj->id().id);
ctx.index = obj_idx;
ctx.source = "object";
fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx);
}
return;
}
if (volume_id < 0) return;
obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
std::string name = m_objects_model->GetName(item).ToUTF8().data();
if (obj->volumes[volume_id]->name == name)
return;
const std::string previous_name = obj->volumes[volume_id]->name;
obj->volumes[volume_id]->name = name;
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.id = std::to_string(obj->volumes[volume_id]->id().id);
ctx.index = volume_id;
ctx.source = "volume";
fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx);
}
void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
@@ -3519,7 +3543,14 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx)
obj->delete_connectors();
if (obj->volumes.empty() || !obj->has_solid_mesh()) {
const std::string deleted_obj_name = obj->name;
model.delete_object(idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = deleted_obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
m_objects_model->Delete(m_objects_model->GetItemById(idx));
continue;
}
@@ -4088,6 +4119,13 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed,
const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut());
Expand(m_objects_model->GetParent(item));
{
Slic3r::LifecycleEventContext ctx;
ctx.name = model_object->name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectAdded, ctx);
}
if (!do_info_update)
return;
+8
View File
@@ -13,6 +13,7 @@
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp"
@@ -698,6 +699,13 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) % ap.name;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "arranged";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
m_plater->update();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
+8
View File
@@ -6,6 +6,7 @@
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp"
#include <numeric>
@@ -347,6 +348,13 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
m_plater->arrange();
}
m_plater->update();
{
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "arranged";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
}
m_plater->mark_plate_toolbar_image_dirty();
+7
View File
@@ -5,6 +5,7 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -254,6 +255,12 @@ void OrientJob::finalize(bool canceled, std::exception_ptr &eptr)
mesh.apply();
}
if (!m_selected.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "auto_oriented";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
m_plater->update();
+39 -4
View File
@@ -1,4 +1,5 @@
#include "PrintJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -153,6 +154,18 @@ void PrintJob::process(Ctl &ctl)
int result = -1;
std::string http_body;
const auto mark_lifecycle_started = [this]() {
if (m_lifecycle_started)
return;
LifecycleEventContext start_ctx;
start_ctx.name = m_project_name;
start_ctx.device_id = m_dev_id;
start_ctx.source = "print_job";
fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx);
m_lifecycle_started = true;
};
int total_plate_num = plate_data.plate_count;
if (!plate_data.is_valid) {
total_plate_num = m_plater->get_partplate_list().get_plate_count();
@@ -174,7 +187,7 @@ void PrintJob::process(Ctl &ctl)
}
}
m_project_name = truncate_string(m_project_name, 100);
const std::string transport_project_name = truncate_string(m_project_name, 100);
int curr_plate_idx = 0;
if (m_print_type == "from_normal") {
@@ -373,11 +386,11 @@ void PrintJob::process(Ctl &ctl)
}
}
if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", m_project_name, curr_plate_idx).ToStdString(); }
if (params.project_name.empty()) {params.project_name = m_project_name;}
if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", transport_project_name, curr_plate_idx).ToStdString(); }
if (params.project_name.empty()) {params.project_name = transport_project_name;}
if (m_is_calibration_task) {
params.project_name = m_project_name;
params.project_name = transport_project_name;
params.origin_model_id = "";
}
@@ -544,6 +557,7 @@ void PrintJob::process(Ctl &ctl)
if (m_print_type == "from_sdcard_view") {
BOOST_LOG_TRIVIAL(info) << "print_job: try to send with cloud, model is sdcard view";
ctl.update_status(curr_percent, _u8L("Sending print job through cloud service"));
mark_lifecycle_started();
result = m_agent->start_sdcard_print(params, update_fn, cancel_fn);
} else if (params.connection_type != "lan") {
if (params.dev_ip.empty())
@@ -567,6 +581,7 @@ void PrintJob::process(Ctl &ctl)
BOOST_LOG_TRIVIAL(info) << "print_job: use ftp send print only";
ctl.update_status(curr_percent, _u8L("Sending print job over LAN"));
is_try_lan_mode = true;
mark_lifecycle_started();
result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn);
if (result < 0) {
error_text = wxString::Format(_L("Access code:%s IP address:%s"), params.password, params.dev_ip);
@@ -583,6 +598,7 @@ void PrintJob::process(Ctl &ctl)
// try to send local with record
BOOST_LOG_TRIVIAL(info) << "print_job: try to start local print with record";
ctl.update_status(curr_percent, _u8L("Sending print job over LAN"));
mark_lifecycle_started();
result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn);
if (result == 0) {
params.comments = "";
@@ -598,18 +614,21 @@ void PrintJob::process(Ctl &ctl)
// try to send with cloud
BOOST_LOG_TRIVIAL(warning) << "print_job: try to send with cloud";
ctl.update_status(curr_percent, _u8L("Sending print job through cloud service"));
// Started was already emitted before the local attempt.
result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn);
}
}
else {
BOOST_LOG_TRIVIAL(info) << "print_job: send with cloud";
ctl.update_status(curr_percent, _u8L("Sending print job through cloud service"));
mark_lifecycle_started();
result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn);
}
}
} else {
if (this->could_emmc_print) {
ctl.update_status(curr_percent, _u8L("Sending print job over LAN"));
mark_lifecycle_started();
result = m_agent->start_local_print(params, update_fn, cancel_fn);
} else {
switch(this->sdcard_state) {
@@ -620,6 +639,7 @@ void PrintJob::process(Ctl &ctl)
if(this->has_sdcard) {
// means the storage is abnormal but can be used option is enabled
ctl.update_status(curr_percent, _u8L("Sending print job over LAN, but the Storage in the printer is abnormal and print-issues may be caused by this."));
mark_lifecycle_started();
result = m_agent->start_local_print(params, update_fn, cancel_fn);
break;
}
@@ -630,6 +650,7 @@ void PrintJob::process(Ctl &ctl)
return;
case DevStorage::SdcardState::HAS_SDCARD_NORMAL:
ctl.update_status(curr_percent, _u8L("Sending print job over LAN"));
mark_lifecycle_started();
result = m_agent->start_local_print(params, update_fn, cancel_fn);
break;
default:
@@ -687,6 +708,7 @@ void PrintJob::process(Ctl &ctl)
}
wxQueueEvent(m_plater, evt);
m_job_finished = true;
m_lifecycle_success = true;
}
}
@@ -699,6 +721,19 @@ void PrintJob::finalize(bool canceled, std::exception_ptr &eptr) {
eptr = std::current_exception();
}
if (m_lifecycle_started && !m_lifecycle_finished) {
LifecycleEventContext finish_ctx;
finish_ctx.name = m_project_name;
finish_ctx.device_id = m_dev_id;
finish_ctx.source = "print_job";
finish_ctx.code = canceled ? LifecycleEvtCode::Warn :
(eptr || !m_lifecycle_success ? LifecycleEvtCode::Error : LifecycleEvtCode::Ok);
finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" :
(m_lifecycle_success ? "" : "failed"));
fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx);
m_lifecycle_finished = true;
}
if (canceled || eptr)
return;
}
+3
View File
@@ -43,6 +43,9 @@ class PrintJob : public Job
std::function<void()> m_success_fun{nullptr};
std::string m_dev_id;
bool m_job_finished{ false };
bool m_lifecycle_started{ false };
bool m_lifecycle_finished{ false };
bool m_lifecycle_success{ false };
int m_print_job_completed_id = 0;
wxString m_completed_evt_data;
std::function<void()> m_enter_ip_address_fun_fail{ nullptr };
+21
View File
@@ -1,4 +1,5 @@
#include "SendJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r/MTUtils.hpp"
@@ -148,6 +149,13 @@ void SendJob::process(Ctl &ctl)
}
}
LifecycleEventContext start_ctx;
start_ctx.name = m_project_name;
start_ctx.device_id = m_dev_id;
start_ctx.source = "send_job";
fire_lifecycle_event(LifecycleEvent::SendJobStarted, start_ctx);
m_lifecycle_started = true;
int total_plate_num = m_plater->get_partplate_list().get_plate_count();
PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx);
@@ -424,6 +432,19 @@ void SendJob::finalize(bool canceled, std::exception_ptr &eptr)
eptr = std::current_exception();
}
if (m_lifecycle_started && !m_lifecycle_finished) {
LifecycleEventContext finish_ctx;
finish_ctx.name = m_project_name;
finish_ctx.device_id = m_dev_id;
finish_ctx.source = "send_job";
finish_ctx.code = canceled ? LifecycleEvtCode::Warn :
(eptr ? LifecycleEvtCode::Error : (m_job_finished ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error));
finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" :
(m_job_finished ? "" : "failed"));
fire_lifecycle_event(LifecycleEvent::SendJobFinished, finish_ctx);
m_lifecycle_finished = true;
}
if (canceled || eptr)
return;
}
+2
View File
@@ -22,6 +22,8 @@ class SendJob : public Job
PrintPrepareData job_data;
std::string m_dev_id;
bool m_job_finished{ false };
bool m_lifecycle_started{ false };
bool m_lifecycle_finished{ false };
int m_print_job_completed_id = 0;
bool m_is_check_mode{false};
bool m_check_and_continue{false};
+34 -1
View File
@@ -29,6 +29,7 @@
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
@@ -2626,11 +2627,20 @@ void PartPlate::set_plate_name(const std::string& name)
if (boost::equals(m_name, name))
return;
const std::string previous_name = m_name;
m_name = name;
if (m_print != nullptr)
m_print->set_plate_name(name);
invalidate_plate_name_texture();
if (m_plater != nullptr && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.index = m_plate_index;
fire_lifecycle_event(LifecycleEvent::PlateRenamed, ctx);
}
}
//get the print's object, result and index
@@ -4741,9 +4751,16 @@ int PartPlateList::create_plate(bool adjust_position)
if (m_plater) {
// In GUI mode
wxGetApp().obj_list()->on_plate_added(plate);
wxGetApp().obj_list()->on_plate_added(plate);
}
if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = plate->get_plate_name();
ctx.index = new_index;
fire_lifecycle_event(LifecycleEvent::PlateCreated, ctx);
}
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":created a new plate %1%") % new_index;
return new_index;
}
@@ -4842,6 +4859,7 @@ int PartPlateList::delete_plate(int index)
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":plate %1%, has an invalid index %2%") % index % plate->get_index();
return -1;
}
const std::string plate_name = plate->get_plate_name();
if (m_plater) {
// In GUI mode
@@ -4924,6 +4942,13 @@ int PartPlateList::delete_plate(int index)
delete plate;
if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = plate_name;
ctx.index = index;
fire_lifecycle_event(LifecycleEvent::PlateDeleted, ctx);
}
// FIX: context of BackgroundSliceProcess and gcode preview need to be updated before ObjectList::reload_all_plates().
#if 0
if (m_plater != nullptr) {
@@ -5032,6 +5057,7 @@ int PartPlateList::select_plate(int index)
if (m_plate_list.empty() || index >= m_plate_list.size()) {
return -1;
}
const int previous_index = m_current_plate;
// BBS: erase unnecessary snapshot
if (get_curr_plate_index() != index && m_intialized) {
@@ -5058,6 +5084,13 @@ int PartPlateList::select_plate(int index)
//wxQueueEvent(m_plater, new SimpleEvent(EVT_GLCANVAS_PLATE_SELECT));
}
if (previous_index != index && m_intialized && m_plater != nullptr && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = m_plate_list[index]->get_plate_name();
ctx.index = index;
fire_lifecycle_event(LifecycleEvent::PlateSelected, ctx);
}
return 0;
}
+105 -13
View File
@@ -57,6 +57,7 @@
#include <wx/aui/aui.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/DRC.hpp"
#include "libslic3r/Format/STEP.hpp"
@@ -10347,7 +10348,14 @@ void Plater::priv::remove(size_t obj_idx)
view3D->enable_layers_editing(false);
m_worker.cancel_all();
std::string obj_name = (obj_idx < model.objects.size()) ? model.objects[obj_idx]->name : std::to_string(obj_idx);
model.delete_object(obj_idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
//BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1);
update();
@@ -10380,7 +10388,14 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia
if (obj->is_cut())
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
std::string obj_name = obj->name;
model.delete_object(obj_idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
//BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1);
@@ -10426,10 +10441,22 @@ void Plater::priv::delete_all_objects_from_model()
void Plater::priv::reset(bool apply_presets_change)
{
// TakeSnapshot below and load_current_presets() further down each re-evaluate the
// aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle
// is_dirty() back and forth several times before it settles; coalesce those into one event.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(dirty_state);
Plater::TakeSnapshot snapshot(q, _u8L("Reset Project"), UndoRedo::SnapshotType::ProjectSeparator);
clear_warnings();
const std::string closed_project_name = into_u8(get_project_filename());
if (!closed_project_name.empty() || !model.objects.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.name = closed_project_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectClosed, ctx);
}
// A new project must not inherit the previous project's published selection (Feature A/B).
m_has_pending_published = false;
m_pending_published_keys.clear();
@@ -12857,11 +12884,15 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
notification_manager->set_slicing_progress_export_possible();
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
const std::string lifecycle_job_name = this->background_process.fff_print() ?
this->background_process.fff_print()->output_filename() : std::string();
this->background_process.reset_export();
// This bool stops showing export finished notification even when process_completed_with_error is false
bool has_error = false;
std::string lifecycle_error_msg;
if (evt.error()) {
auto message = evt.format_error_message();
lifecycle_error_msg = message.first;
if (evt.critical_error()) {
if (q->m_tracking_popup_menu) {
// We don't want to pop-up a message box when tracking a pop-up menu.
@@ -12899,6 +12930,14 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
is_finished = true;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = lifecycle_job_name;
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
}
//BBS: set the current plater's slice result to valid
if (!this->background_process.empty())
this->background_process.get_current_plate()->update_slice_result_valid_state(evt.success());
@@ -15432,21 +15471,33 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
//get_partplate_list().reinit();
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
//p->preview->update_gcode_result(p->partplate_list.get_current_slice_result());
reset(transfer_preset_changes);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
update_project_dirty_from_presets();
if (!silent) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::NewProject, ctx);
}
{
// Same rationale as in Plater::priv::reset(): the whole reset + preset-reload +
// baseline-reset sequence below settles into its final dirty state only once it
// completes, so hold notifications until then to avoid firing on transient flips.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state);
//reset project
p->project.reset();
//set project name
if (project_name.empty())
p->set_project_name(_L("Untitled"));
else
p->set_project_name(project_name);
reset(transfer_preset_changes);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
update_project_dirty_from_presets();
Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator);
//reset project
p->project.reset();
//set project name
if (project_name.empty())
p->set_project_name(_L("Untitled"));
else
p->set_project_name(project_name);
Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator);
}
Model m;
model().load_from(m); // new id avoid same path name
@@ -15564,6 +15615,13 @@ void Plater::load_project(wxString const& filename2,
p->set_project_name(_L("Untitled"));
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(load_restore ? originfile : filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectOpened, ctx);
}
} else {
if (using_exported_file()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename;
@@ -15635,11 +15693,23 @@ int Plater::save_project(bool saveAs)
if (full_pathnames) {
save_strategy = save_strategy | SaveStrategy::FullPathSources;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectBeforeSave, ctx);
}
if (export_3mf(into_path(filename), save_strategy) < 0) {
MessageDialog(this, _L("Failed to save the project.\nPlease check whether the folder exists online or if other programs have the project file open."),
_L("Save project"), wxOK | wxICON_WARNING).ShowModal();
return wxID_CANCEL;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectAfterSave, ctx);
}
Slic3r::remove_backup(model(), false);
@@ -20972,6 +21042,14 @@ void Plater::changed_object(ModelObject &object){
// Check outside bed
get_current_canvas3D()->requires_check_outside_state();
if (!is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = object.name;
ctx.id = std::to_string(object.id().id);
ctx.source = "geometry";
fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx);
}
}
void Plater::changed_object(int obj_idx)
@@ -21008,6 +21086,20 @@ void Plater::changed_objects(const std::vector<size_t>& object_idxs)
// update print
this->p->schedule_background_process();
if (!is_loading_project()) {
for (size_t obj_idx : object_idxs) {
if (obj_idx >= p->model.objects.size() || p->model.objects[obj_idx] == nullptr)
continue;
LifecycleEventContext ctx;
ctx.name = p->model.objects[obj_idx]->name;
ctx.id = std::to_string(p->model.objects[obj_idx]->id().id);
ctx.index = static_cast<int>(obj_idx);
ctx.source = "geometry";
fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx);
}
}
}
void Plater::schedule_background_process(bool schedule/* = true*/)
+26
View File
@@ -1932,6 +1932,15 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_realistic_phong);
auto item_realistic_preview = create_item_checkbox(
_L("Enable in Preview"),
_L("Also applies realistic view to the Preview canvas, not just Prepare.\n"
"Preview draws the full toolpath geometry, so shadows and SSAO cost considerably"
" more there than on a plain model."),
SETTING_OPENGL_REALISTIC_PREVIEW
);
g_sizer->Add(item_realistic_preview);
auto item_realistic_ssao = create_item_checkbox(
_L("SSAO ambient occlusion"),
_L("Applies SSAO in realistic view."),
@@ -2019,6 +2028,23 @@ void PreferencesDialog::create_items()
//// GRAPHICS > G-code Preview
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
// ORCA: view type the preview opens with
std::vector<wxString> PreviewViewTypeLabels;
std::vector<std::string> PreviewViewTypeValues;
for (const auto& [value, label] : GCodeViewer::default_view_type_choices()) {
PreviewViewTypeValues.push_back(value);
PreviewViewTypeLabels.push_back(from_u8(label));
}
auto item_preview_view_type = create_item_combobox(
_L("Default view type"),
_L("The color scheme the sliced preview opens with.\n"
"Automatic: Filament for multi material prints, Line Type for single material ones.\n"
"Last used: the view type you selected last.\n"
"Any other value always opens that view type.\n"
"You can still switch the view type in the preview afterwards."),
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
g_sizer->Add(item_preview_view_type);
auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"),
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
+46 -1
View File
@@ -6,6 +6,7 @@
#include "MainFrame.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include <boost/algorithm/string/predicate.hpp>
@@ -17,13 +18,23 @@ namespace GUI {
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{
const bool was_dirty = is_dirty();
m_plater_dirty = dirty;
notify_dirty_change(was_dirty, "undo_redo");
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::set_plater_dirty(bool is_dirty)
{
const bool was_dirty = this->is_dirty();
m_plater_dirty = is_dirty;
notify_dirty_change(was_dirty, "plater");
}
void ProjectDirtyStateManager::update_from_presets()
{
const bool was_dirty = is_dirty();
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
GUI_App &app = wxGetApp();
@@ -45,18 +56,53 @@ void ProjectDirtyStateManager::update_from_presets()
}
m_presets_dirty |= app.has_unsaved_preset_changes();
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
notify_dirty_change(was_dirty, "presets");
app.mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
{
const bool was_dirty = is_dirty();
this->reset_initial_presets();
m_plater_dirty = false;
m_presets_dirty = false;
m_project_config_dirty = false;
notify_dirty_change(was_dirty, "save");
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::notify_dirty_change(bool was_dirty, const char *source)
{
if (m_suppress_depth > 0)
return;
const bool dirty = is_dirty();
if (was_dirty == dirty)
return;
LifecycleEventContext ctx;
ctx.code = LifecycleEvtCode::Ok;
ctx.dirty = dirty;
ctx.source = source;
fire_lifecycle_event(LifecycleEvent::ProjectDirtyChanged, ctx);
}
void ProjectDirtyStateManager::begin_suppress_notifications()
{
if (m_suppress_depth == 0)
m_suppress_entry_dirty = is_dirty();
++m_suppress_depth;
}
void ProjectDirtyStateManager::end_suppress_notifications()
{
assert(m_suppress_depth > 0);
if (m_suppress_depth > 0)
--m_suppress_depth;
if (m_suppress_depth == 0)
notify_dirty_change(m_suppress_entry_dirty, "batch");
}
void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets.fill(std::string{});
@@ -168,4 +214,3 @@ void ProjectDirtyStateManager::render_debug_window() const
} // namespace GUI
} // namespace Slic3r
+22 -1
View File
@@ -14,21 +14,42 @@ public:
void reset_after_save();
void reset_initial_presets();
void set_plater_dirty(bool is_dirty) { m_plater_dirty = is_dirty; }
void set_plater_dirty(bool is_dirty);
bool is_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; }
bool is_presets_dirty() const { return m_presets_dirty; }
// RAII guard coalescing dirty-state updates: while any guard is alive, ProjectDirtyChanged
// notifications are held back; when the outermost guard is destroyed, at most one
// notification fires, reflecting only the net change across the whole guarded scope.
class NotificationSuppressor
{
public:
explicit NotificationSuppressor(ProjectDirtyStateManager &owner) : m_owner(owner) { m_owner.begin_suppress_notifications(); }
~NotificationSuppressor() { m_owner.end_suppress_notifications(); }
NotificationSuppressor(const NotificationSuppressor &) = delete;
NotificationSuppressor &operator=(const NotificationSuppressor &) = delete;
private:
ProjectDirtyStateManager &m_owner;
};
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
private:
void notify_dirty_change(bool was_dirty, const char *source);
void begin_suppress_notifications();
void end_suppress_notifications();
// Does the Undo / Redo stack indicate the project is dirty?
bool m_plater_dirty { false };
// Do the presets indicate the project is dirty?
bool m_presets_dirty { false };
// Is the project config dirty?
bool m_project_config_dirty { false };
// NotificationSuppressor nesting depth and the dirty state observed when the outermost guard began.
int m_suppress_depth { 0 };
bool m_suppress_entry_dirty { false };
// Keeps track of preset names selected at the time of last project save.
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
DynamicPrintConfig m_initial_project_config;
+53 -1
View File
@@ -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();
@@ -1823,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));
}
@@ -2093,7 +2145,7 @@ InputIpAddressDialog::~InputIpAddressDialog()
void InputIpAddressDialog::on_dpi_changed(const wxRect& suggested_rect)
{
m_button_cafile->Rescale();
}
+4
View File
@@ -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 };
+2 -2
View File
@@ -4740,11 +4740,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 (...) {
+15
View File
@@ -38,6 +38,7 @@
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
@@ -6964,6 +6965,20 @@ bool Tab::select_preset(
}
load_current_preset();
{
Slic3r::LifecycleEventContext ctx;
ctx.name = preset_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
switch (m_type) {
case Preset::TYPE_PRINT: ctx.msg = "print"; break;
case Preset::TYPE_SLA_PRINT: ctx.msg = "sla_print"; break;
case Preset::TYPE_FILAMENT: ctx.msg = "filament"; break;
case Preset::TYPE_SLA_MATERIAL: ctx.msg = "sla_material"; break;
case Preset::TYPE_PRINTER: ctx.msg = "printer"; break;
default: break;
}
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::PresetSelected, ctx);
}
if (delete_third_printer) {
wxGetApp().CallAfter([filament_presets, process_presets]() {
+34 -4
View File
@@ -1,10 +1,13 @@
#include "TaskManager.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "nlohmann/json.hpp"
#include "MainFrame.hpp"
#include "GUI_App.hpp"
#include <exception>
using namespace nlohmann;
namespace Slic3r {
@@ -214,17 +217,34 @@ int TaskManager::schedule(TaskStateInfo* task)
boost::thread* new_sending_thread = new boost::thread();
*new_sending_thread = Slic3r::create_thread(
[this, task] {
// Keep both lifecycle callbacks on this per-task worker thread. Plugin observers can
// therefore associate Started and Finished for one task with a single execution context.
LifecycleEventContext start_ctx;
start_ctx.name = task->params().project_name;
start_ctx.device_id = task->params().dev_id;
start_ctx.job_id = std::to_string(task->task_info_id);
start_ctx.source = "task_manager";
fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx);
int result = -1;
if (!m_agent) {
BOOST_LOG_TRIVIAL(trace) << "task_manager: NetworkAgent is nullptr";
return;
}
assert(m_agent);
else {
assert(m_agent);
try {
// DEBUG FOR TEST
#if 0
int result = start_print_test(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
result = start_print_test(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
#else
int result = m_agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
result = m_agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn);
#endif
} catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(error) << "task_manager: start_print threw: " << ex.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "task_manager: start_print threw an unknown exception";
}
}
if (result == 0) {
last_sent_timestamp = std::chrono::system_clock::now();
task->set_sent_time(last_sent_timestamp);
@@ -237,6 +257,16 @@ int TaskManager::schedule(TaskStateInfo* task)
task->set_state(TaskState::TS_SEND_CANCELED);
}
}
LifecycleEventContext finish_ctx;
finish_ctx.name = task->params().project_name;
finish_ctx.device_id = task->params().dev_id;
finish_ctx.job_id = std::to_string(task->task_info_id);
finish_ctx.source = "task_manager";
finish_ctx.code = result == 0 ? LifecycleEvtCode::Ok :
(task->is_canceled() ? LifecycleEvtCode::Warn : LifecycleEvtCode::Error);
finish_ctx.msg = result == 0 ? "" : (task->is_canceled() ? "cancelled" : "failed");
fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx);
/* remove from sending task list */
m_scedule_mutex.lock();
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "IMediaController.hpp"
#include <slic3r/Utils/IPrinterAgent.hpp>
#include <slic3r/Utils/ICameraSignalingChannel.hpp>
#include <wx/image.h>
+29 -27
View File
@@ -4,6 +4,8 @@
#include "NetworkAgentFactory.hpp"
#include "libslic3r/Utils.hpp"
#include "NetworkAgent.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
@@ -148,36 +150,27 @@ void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
// Communication
// ============================================================================
std::string BBLPrinterAgent::ams_refresh_rfid_gcode(const std::string& tray_id)
int BBLPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode)
{
return (boost::format("M620 R%1% \n") % tray_id).str();
}
std::string BBLPrinterAgent::ams_calibrate_gcode(int ams_id)
{
return (boost::format("M620 C%1% \n") % ams_id).str();
}
std::string BBLPrinterAgent::ams_select_tray_gcode(const std::string& tray_id)
{
return (boost::format("M620 P%1% \n") % tray_id).str();
}
int BBLPrinterAgent::command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
{
const std::string gcode = ams_refresh_rfid_gcode(tray_id);
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode;
nlohmann::json j;
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
j["print"]["sequence_id"] = std::to_string(sequence_id);
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 = ams_calibrate_gcode(ams_id);
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode;
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;
@@ -187,8 +180,7 @@ int BBLPrinterAgent::command_ams_calibrate(std::string dev_id, int ams_id, int s
int BBLPrinterAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
{
const std::string gcode = ams_select_tray_gcode(tray_id);
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode;
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;
@@ -273,13 +265,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;
}
+2 -5
View File
@@ -29,16 +29,13 @@ public:
// Communication
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
static std::string ams_refresh_rfid_gcode(const std::string& tray_id);
static std::string ams_calibrate_gcode(int ams_id);
static std::string ams_select_tray_gcode(const std::string& tray_id);
int command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) 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(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;
+13 -4
View File
@@ -13,11 +13,9 @@
#include <memory>
#include <vector>
#include <functional>
#include <cstdint>
#include <cmath>
#include <nlohmann/json.hpp>
#include <boost/format.hpp>
#include "ICameraSignalingChannel.hpp"
namespace Slic3r {
@@ -36,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.
*
@@ -103,7 +112,7 @@ public:
// 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, std::string, int, bool)
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; }
@@ -205,7 +214,7 @@ public:
/**
* 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.
+70 -52
View File
@@ -341,9 +341,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;
}
@@ -352,7 +352,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;
@@ -379,7 +379,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;
@@ -427,7 +427,7 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
// so the name falls back to the IP instead of blank. (matches
// feature/printer-agent-port-pristine; the IP is what shipped before the port)
// note: dummy id/creds; use_ssl false because Moonraker/print-host is http.
init_device_info(dev_ip, dev_ip, "", "", false);
init_device_info(dev_ip, dev_ip, "", "", false, "");
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;
@@ -575,12 +575,11 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
return BAMBU_NETWORK_ERR_CANCELED;
}
// Start print via Moonraker's print API, referencing the file we just uploaded.
// Start print via Moonraker's G-code script endpoint, referencing the file we just uploaded.
if (update_fn)
update_fn(PrintingStageSending, 0, "Starting print...");
std::string start_error;
if (!start_print_file(device_info.base_url, device_info.api_key, upload_filename, start_error)) {
std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename;
if (!send_gcode_sync(device_info.dev_id, gcode)) {
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
}
@@ -1047,12 +1046,8 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
}
response["print"]["param"] = gcode;
auto [base_url, api_key] = connection_snapshot();
enqueue_command([this, dev_id, response = std::move(response), base_url = std::move(base_url),
api_key = std::move(api_key)]() mutable {
response["print"]["result"] = send_gcode(dev_id, response["print"]["param"].get<std::string>(), base_url, api_key)
? "success"
: "failed";
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;
@@ -1086,11 +1081,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);
auto [base_url, api_key] = connection_snapshot();
enqueue_command([this, dev_id, gcode = std::move(gcode), base_url = std::move(base_url),
api_key = std::move(api_key)] {
send_gcode(dev_id, gcode, base_url, api_key);
});
send_gcode_async(dev_id, gcode);
return BAMBU_NETWORK_SUCCESS;
}
}
@@ -1105,11 +1096,7 @@ 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);
auto [base_url, api_key] = connection_snapshot();
enqueue_command([this, dev_id, gcode = std::move(gcode), base_url = std::move(base_url),
api_key = std::move(api_key)] {
send_gcode(dev_id, gcode, base_url, api_key);
});
send_gcode_async(dev_id, gcode);
return BAMBU_NETWORK_SUCCESS;
}
}
@@ -1117,10 +1104,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
// why: no current OrcaSlicer sender emits the "home" discriminator;
// GUI homing uses gcode_line with G28 instead.
if (cmd == "home") {
auto [base_url, api_key] = connection_snapshot();
enqueue_command([this, dev_id, base_url = std::move(base_url), api_key = std::move(api_key)] {
send_gcode(dev_id, "G28", base_url, api_key);
});
send_gcode_async(dev_id, "G28");
return BAMBU_NETWORK_SUCCESS;
}
}
@@ -1150,7 +1134,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
}
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;
@@ -1160,13 +1144,13 @@ 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.use_ssl = use_ssl;
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;
@@ -1573,7 +1557,56 @@ bool MoonrakerPrinterAgent::post_print_action(const std::string& action,
return true;
}
void MoonrakerPrinterAgent::send_gcode_async(const std::string& dev_id, const std::string& gcode,
std::function<void(bool)> on_result) const
{
(void) dev_id;
std::string base_url;
std::string api_key;
{
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
base_url = device_info.base_url;
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(const std::string& dev_id, const std::string& gcode) const
{
return send_gcode_sync(dev_id, gcode);
}
bool MoonrakerPrinterAgent::send_gcode_sync(const std::string& dev_id, const std::string& gcode) const
{
// why: snapshot then release - see post_print_action.
std::string base_url, api_key;
@@ -2678,7 +2711,7 @@ 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;
device_info.nozzle_diameter = fetched_info.nozzle_diameter;
@@ -2726,26 +2759,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;
}
+6 -3
View File
@@ -64,7 +64,7 @@ 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;
@@ -123,7 +123,7 @@ protected:
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, const TrayInfoResolver& vendor_resolver);
// 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;
static float parse_nozzle_diameter(const nlohmann::json& response);
@@ -138,7 +138,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;
@@ -168,6 +168,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_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);
+6 -9
View File
@@ -1,13 +1,10 @@
#include <stdio.h>
#include "NetworkAgent.hpp"
#include <stdlib.h>
#include <set>
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <nlohmann/json.hpp>
#include "IPrinterAgent.hpp"
#include "libslic3r/Utils.hpp"
#include "NetworkAgent.hpp"
#include "BBLNetworkPlugin.hpp"
namespace Slic3r {
@@ -796,10 +793,10 @@ int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos
return -1;
}
int NetworkAgent::command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
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->command_ams_refresh_rfid(dev_id, tray_id, sequence_id, lan_mode);
return m_printer_agent->command_ams_refresh_rfid(dev_id, ams_id, slot_id, sequence_id, lan_mode);
return -1;
}
@@ -867,10 +864,10 @@ int NetworkAgent::command_axis_control(std::string dev_id, std::string axis, dou
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::connect_printer(const PrinterConnectionParams& params)
{
if (m_printer_agent)
return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl);
return m_printer_agent->connect_printer(params);
return -1;
}
+2 -2
View File
@@ -150,7 +150,7 @@ 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 command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode);
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);
@@ -161,7 +161,7 @@ public:
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(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
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;
+49 -39
View File
@@ -1,16 +1,22 @@
#include "OrcaPrinterAgent.hpp"
#include "AmsPayload.hpp"
#include "Http.hpp"
#include "IPrinterAgent.hpp"
#include "Http.hpp"
#include "NetworkAgentFactory.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "bambu_networking.hpp"
#include <algorithm>
#include <atomic>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <algorithm>
#include <atomic>
#include <array>
#include <chrono>
#include <cctype>
@@ -20,8 +26,6 @@
#include <cmath>
#include <limits>
#include <mutex>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <random>
#include <set>
#include <sstream>
@@ -30,9 +34,6 @@
#include <unordered_map>
#include <utility>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
namespace Slic3r {
const std::string OrcaPrinterAgent_VERSION = "0.0.1";
@@ -776,21 +777,28 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int /*qos*/, int /*flag*/)
{ return route_send(/*is_lan=*/false, dev_id, json_str); }
int OrcaPrinterAgent::command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
int OrcaPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int tray_id, int sequence_id, bool lan_mode)
{
int tray_number = 0;
if (!parse_nonnegative_command_id(tray_id, tray_number)) {
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid RFID tray id=" << tray_id;
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
(void) ams_id;
// int tray_number = 0;
// if (!parse_nonnegative_command_id(tray_id, tray_number)) {
// BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid RFID tray id=" << tray_id;
// return BAMBU_NETWORK_ERR_INVALID_HANDLE;
// }
nlohmann::json j;
j["print"]["command"] = "ams_get_rfid";
j["print"]["sequence_id"] = std::to_string(sequence_id);
j["print"]["tray_id"] = tray_number;
j["print"]["tray_id"] = tray_id;
return route_send(lan_mode, dev_id, j.dump());
}
int OrcaPrinterAgent::command_ams_calibrate(std::string /*dev_id*/, int /*ams_id*/, int /*sequence_id*/, bool /*lan_mode*/)
{
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: AMS calibration is not part of the OrcaSonar API";
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
}
std::string OrcaPrinterAgent::build_ams_change_filament_body(int tray_number, int sequence_id)
{
nlohmann::json j;
@@ -1117,30 +1125,32 @@ void OrcaPrinterAgent::on_connected(const std::string& dev_id, OrcaMqttConnectio
[conn, dev_id](const std::string& body) { conn->send_request(dev_id, body); }); // dev_id captured BY VALUE
}
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)
{
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: connect_printer requested dev_id=" << dev_id << " dev_ip=" << dev_ip
<< " username=" << (username.empty() ? "<default>" : username) << " password_present=" << (!password.empty())
<< " use_ssl=" << use_ssl;
(void) use_ssl; // OrcaSonar LAN is plaintext ws://
if (dev_id.empty() || dev_ip.empty()) {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: connect_printer requested dev_id=" << params.dev_id << " dev_ip=" << params.host
<< " username=" << (params.username.empty() ? "<default>" : params.username) << " password_present=" << (!params.password.empty())
<< " use_ssl=" << params.use_ssl;
(void) params.use_ssl; // OrcaSonar LAN is plaintext ws://
if (params.dev_id.empty() || params.host.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: connect_printer rejected missing dev_id or dev_ip";
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
std::string host, port;
if (!parse_lan_endpoint(dev_ip, host, port)) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: connect_printer rejected unparsable LAN endpoint dev_ip=" << dev_ip;
if (!parse_lan_endpoint(params.host, host, port)) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: connect_printer rejected unparsable LAN endpoint dev_ip=" << params.host;
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
if (!params.port.empty())
port = params.port;
disconnect_printer();
const uint64_t gen = ++m_lan_generation;
OrcaMqttConnection::Config cfg;
cfg.url = "ws://" + host + ":" + port + "/mqtt";
cfg.use_tls = false;
cfg.username = username.empty() ? std::string("orcasonar") : username;
cfg.password = password;
cfg.client_id = make_lan_client_id(dev_id);
cfg.username = params.username.empty() ? std::string("orcasonar") : params.username;
cfg.password = params.password;
cfg.client_id = make_lan_client_id(params.dev_id);
cfg.keepalive_seconds = 60;
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN connection prepared generation=" << gen << " host=" << host << " port=" << port
@@ -1152,31 +1162,31 @@ int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, st
{
std::lock_guard<std::mutex> l(state_mutex);
previous_connection = m_current_connection;
m_lan_dev_id = dev_id;
m_lan_dev_id = params.dev_id;
m_lan_url = cfg.url;
m_lan_password = password; // access code; the façade key is bootstrapped lazily
m_lan_password = params.password; // access code; the façade key is bootstrapped lazily
m_lan_api_key.clear();
m_lan_api_key_gen = gen;
m_lan_api_key_gen = gen;
m_camera_stream_mode = CameraStreamMode::none;
m_camera_url.clear();
m_current_connection = LAN;
lan_mqtt_connection = std::make_unique<OrcaMqttConnection>();
conn = lan_mqtt_connection.get();
}
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: selected LAN printer dev_id=" << dev_id
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: selected LAN printer dev_id=" << params.dev_id
<< " transport=" << connection_type_name(previous_connection) << "->LAN";
if (m_lan_connect_thread.joinable())
m_lan_connect_thread.join(); // disconnect_printer() above already stopped the old conn, so this is fast
m_lan_connect_thread = std::thread([this, conn, cfg, dev_id, gen] {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN connect worker started generation=" << gen << " dev_id=" << dev_id
m_lan_connect_thread = std::thread([this, conn, cfg, params, gen] {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN connect worker started generation=" << gen << " dev_id=" << params.dev_id
<< " url=" << cfg.url;
if (gen != m_lan_generation.load()) {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN connect worker abandoned before start generation=" << gen
<< " current_generation=" << m_lan_generation.load();
return; // superseded before we ran: never raise a socket nobody will tear down
}
const bool ok = conn->start(cfg, make_lan_message_handler(gen), [this, gen, dev_id, conn](bool connected, bool initial) {
const bool ok = conn->start(cfg, make_lan_message_handler(gen), [this, gen, params, conn](bool connected, bool initial) {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN MQTT state callback connected=" << connected << " initial=" << initial
<< " generation=" << gen << " current_generation=" << m_lan_generation.load()
<< " connack_rc=" << conn->last_connack_rc();
@@ -1185,18 +1195,18 @@ int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, st
return;
}
if (connected && !initial) {
on_connected(dev_id, conn, gen);
dispatch_local_connect(ConnectStatusOk, dev_id, "0");
on_connected(params.dev_id, conn, gen);
dispatch_local_connect(ConnectStatusOk, params.dev_id, "0");
} else if (!connected && !initial) {
dispatch_local_connect(ConnectStatusLost, dev_id, "connection_lost");
dispatch_local_connect(ConnectStatusLost, params.dev_id, "connection_lost");
}
});
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN MQTT start returned ok=" << ok << " generation=" << gen
<< " current_generation=" << m_lan_generation.load() << " connected=" << conn->is_connected()
<< " running=" << conn->is_running() << " connack_rc=" << conn->last_connack_rc();
if (ok && gen == m_lan_generation.load()) {
on_connected(dev_id, conn, gen);
dispatch_local_connect(ConnectStatusOk, dev_id, "0");
on_connected(params.dev_id, conn, gen);
dispatch_local_connect(ConnectStatusOk, params.dev_id, "0");
} else if (!ok && gen == m_lan_generation.load() && !conn->is_running()) {
// A refusal with rc 4/5 terminates the transport. Network errors keep
// retrying in OrcaMqttConnection, so leave the UI in its connecting state.
@@ -1204,7 +1214,7 @@ int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, st
const std::string reason = rc >= 0 ? std::to_string(rc) : "initial_connect_failed";
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: LAN MQTT connection terminated before readiness"
<< " generation=" << gen << " connack_rc=" << rc << " reason=" << reason;
dispatch_local_connect(ConnectStatusFailed, dev_id, reason);
dispatch_local_connect(ConnectStatusFailed, params.dev_id, reason);
} else if (!ok && gen == m_lan_generation.load()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: LAN MQTT initial attempt failed but worker is retrying"
<< " generation=" << gen << " connack_rc=" << conn->last_connack_rc();
+3 -2
View File
@@ -41,7 +41,7 @@ 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;
@@ -81,7 +81,8 @@ public:
int set_on_local_message_fn(OnMessageFn fn) override;
int set_queue_on_main_fn(QueueOnMainFn fn) override;
int command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override;
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int tray_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_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode) override;
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) override;
+18
View File
@@ -24,6 +24,7 @@
#include "CrealityPrint.hpp"
#include "../GUI/PrintHostDialogs.hpp"
#include "../GUI/MainFrame.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Obico.hpp"
#include "Flashforge.hpp"
#include "SimplyPrint.hpp"
@@ -360,12 +361,29 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
// Captured before upload_data is moved into upload() below.
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
{
LifecycleEventContext ctx;
ctx.name = upload_filename;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
}
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
{
LifecycleEventContext ctx;
ctx.name = upload_filename;
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
}
if (success) {
emit_progress(100);
if (the_job.switch_to_device_tab) {
+13
View File
@@ -7,6 +7,7 @@
#include "libslic3r/Config.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r_version.h"
@@ -46,6 +47,16 @@ void install_capability_resolver()
});
}
// Global libslic3r-side seam (Slic3r::fire_lifecycle_event, in libslic3r/LifecycleEvents.hpp):
// broadcasts to every loaded, enabled capability regardless of type, unlike the SlicingPipeline
// hook below which only targets picker-selected SlicingPipeline capabilities.
void install_lifecycle_event_hook()
{
set_lifecycle_hook_fn([](LifecycleEvent event, const LifecycleEventContext& ctx) {
PluginManager::instance().dispatch_lifecycle_event(event, ctx);
});
}
// Print::process() fires this hook at each pipeline seam on the slicing worker
// thread; here we run the picker-selected SlicingPipeline capabilities. Per
// capability we acquire the GIL, honor cancellation, and convert a plugin
@@ -122,12 +133,14 @@ void install()
{
install_capability_resolver();
install_slicing_pipeline_hook();
install_lifecycle_event_hook();
}
void uninstall()
{
ConfigBase::set_resolve_capability_fn(nullptr);
Print::set_slicing_pipeline_hook_fn(nullptr);
set_lifecycle_hook_fn(nullptr);
}
} // namespace Slic3r::plugin_hooks
+3 -2
View File
@@ -14,8 +14,9 @@ namespace Slic3r::plugin_hooks {
void install();
// Reset every hook to null so none can enter Python after the interpreter
// finalizes. Called from PluginManager::shutdown(); callers must have stopped
// background slicing first (resetting a hook while process() runs is a race).
// finalizes. The lifecycle-event hook drains callbacks already in progress
// before returning. Other hooks retain their existing caller-side shutdown
// requirements.
void uninstall();
} // namespace Slic3r::plugin_hooks
+32 -1
View File
@@ -1,5 +1,6 @@
#include "PluginManager.hpp"
#include <exception>
#include <libslic3r/Utils.hpp>
#include <memory>
#include <pybind11/embed.h>
@@ -134,7 +135,8 @@ void PluginManager::shutdown()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": PluginManager shutdown enter";
// Detach the libslic3r hooks first so nothing dispatches into Python while (or after) plugins
// unload. Callers stop background slicing before this.
// unload. The lifecycle-event hook also drains callbacks already in progress before returning;
// the remaining hook seams retain their existing shutdown requirements.
plugin_hooks::uninstall();
// Reject new plugin loads before we drain.
@@ -2089,4 +2091,33 @@ ExecutionResult PluginManager::run_script_capability(const std::string& plugin_k
return result;
}
void PluginManager::dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx) {
const auto is_canceled = [&ctx]() {
return ctx.cancellation_check && ctx.cancellation_check();
};
if (is_canceled())
return;
for (const auto& cap : get_plugin_capabilities()) {
if (!cap || !cap->is_enabled()) continue;
if (is_canceled())
break;
try {
cap->on_lifecycle_event(evt, ctx);
} catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name()
<< "' on_lifecycle_event(" << lifecycle_event_to_string(evt) << ") threw: " << ex.what()
<< " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code)
<< ", msg='" << ctx.msg << "']";
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name()
<< "' on_lifecycle_event(" << lifecycle_event_to_string(evt)
<< ") threw a non-standard exception"
<< " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code)
<< ", msg='" << ctx.msg << "']";
}
}
}
} // namespace Slic3r
+3 -1
View File
@@ -8,6 +8,7 @@
#include <condition_variable>
#include <functional>
#include <libslic3r/Config.hpp>
#include <libslic3r/LifecycleEvents.hpp>
#include <map>
#include <memory>
#include <mutex>
@@ -20,7 +21,6 @@
#include <pybind11/embed.h>
#include "CloudPluginService.hpp"
#include "PluginFsUtils.hpp"
#include "PluginDescriptor.hpp"
#include "PluginLoader.hpp"
#include "PluginConfig.hpp"
@@ -210,6 +210,8 @@ public:
ExecutionResult run_script_capability(const std::string& plugin_key, const std::string& capability_name, std::string& error);
void dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx);
private:
PluginManager() = default;
PluginManager(const PluginManager&) = delete;
+5
View File
@@ -132,6 +132,11 @@ public:
{
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
}
void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) override
{
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_lifecycle_event, event, ctx);
}
};
class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline<PluginCapabilityInterface>
+58
View File
@@ -377,6 +377,61 @@ void bind_python_api(pybind11::module_& m)
.value("FatalError", PluginResult::FatalError)
.export_values();
py::enum_<LifecycleEvent>(m, "LifecycleEvent", "Application lifecycle moment passed to on_lifecycle_event")
.value("NewProject", LifecycleEvent::NewProject)
.value("ProjectOpened", LifecycleEvent::ProjectOpened)
.value("ProjectBeforeSave", LifecycleEvent::ProjectBeforeSave)
.value("ProjectAfterSave", LifecycleEvent::ProjectAfterSave)
.value("ProjectClosed", LifecycleEvent::ProjectClosed)
.value("ProjectDirtyChanged", LifecycleEvent::ProjectDirtyChanged)
.value("SliceStarted", LifecycleEvent::SliceStarted)
.value("SliceGeometryFinished", LifecycleEvent::SliceGeometryFinished)
.value("GCodeExportStarted", LifecycleEvent::GCodeExportStarted)
.value("GCodeExportFinished", LifecycleEvent::GCodeExportFinished)
.value("SlicingJobComplete", LifecycleEvent::SlicingJobComplete)
.value("ObjectAdded", LifecycleEvent::ObjectAdded)
.value("ObjectDeleted", LifecycleEvent::ObjectDeleted)
.value("ObjectTransformed", LifecycleEvent::ObjectTransformed)
.value("ObjectChanged", LifecycleEvent::ObjectChanged)
.value("ObjectRenamed", LifecycleEvent::ObjectRenamed)
.value("PlateCreated", LifecycleEvent::PlateCreated)
.value("PlateDeleted", LifecycleEvent::PlateDeleted)
.value("PlateSelected", LifecycleEvent::PlateSelected)
.value("PlateRenamed", LifecycleEvent::PlateRenamed)
.value("PresetSelected", LifecycleEvent::PresetSelected)
.value("PresetSaved", LifecycleEvent::PresetSaved)
.value("PrintStateChanged", LifecycleEvent::PrintStateChanged)
.value("DeviceOnline", LifecycleEvent::DeviceOnline)
.value("DeviceOffline", LifecycleEvent::DeviceOffline)
.value("DeviceDiscovered", LifecycleEvent::DeviceDiscovered)
.value("DeviceSelected", LifecycleEvent::DeviceSelected)
.value("UploadStarted", LifecycleEvent::UploadStarted)
.value("UploadFinished", LifecycleEvent::UploadFinished)
.value("PrintJobStarted", LifecycleEvent::PrintJobStarted)
.value("PrintJobFinished", LifecycleEvent::PrintJobFinished)
.value("SendJobStarted", LifecycleEvent::SendJobStarted)
.value("SendJobFinished", LifecycleEvent::SendJobFinished)
.export_values();
py::enum_<LifecycleEvtCode>(m, "LifecycleEvtCode", "Outcome code accompanying a LifecycleEventContext")
.value("Ok", LifecycleEvtCode::Ok)
.value("Error", LifecycleEvtCode::Error)
.value("Warn", LifecycleEvtCode::Warn)
.export_values();
py::class_<LifecycleEventContext>(m, "LifecycleEventContext", "Payload accompanying a LifecycleEvent")
.def(py::init<>())
.def_readonly("name", &LifecycleEventContext::name)
.def_readonly("code", &LifecycleEventContext::code)
.def_readonly("msg", &LifecycleEventContext::msg)
.def_readonly("id", &LifecycleEventContext::id)
.def_readonly("previous_name", &LifecycleEventContext::previous_name)
.def_readonly("device_id", &LifecycleEventContext::device_id)
.def_readonly("job_id", &LifecycleEventContext::job_id)
.def_readonly("source", &LifecycleEventContext::source)
.def_readonly("index", &LifecycleEventContext::index)
.def_readonly("dirty", &LifecycleEventContext::dirty);
py::class_<PluginContext>(m, "PluginContext", "Context shared with plugin entry points")
.def(py::init<>())
.def_readwrite("orca_version", &PluginContext::orca_version);
@@ -401,6 +456,9 @@ void bind_python_api(pybind11::module_& m)
.def("get_type", &PluginCapabilityInterface::get_type)
.def("on_load", &PluginCapabilityInterface::on_load)
.def("on_unload", &PluginCapabilityInterface::on_unload)
.def("on_lifecycle_event", &PluginCapabilityInterface::on_lifecycle_event,
"Override to react to an application lifecycle moment (LifecycleEvent) and its\n"
"LifecycleEventContext payload. Available on every capability type.")
.def("has_config_ui", &PluginCapabilityInterface::has_config_ui,
"Override to return True to replace the host's default JSON editor with your own HTML\n"
"UI, returned by get_config_ui(). Every capability is configurable and appears in the\n"
@@ -10,6 +10,8 @@
#include <nlohmann/json.hpp>
#include <pybind11/embed.h>
#include <libslic3r/LifecycleEvents.hpp>
namespace Slic3r {
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
@@ -167,6 +169,8 @@ public:
virtual void on_unload() {}
virtual void on_cancelled() {}
virtual void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) { (void) event; (void) ctx; }
// ── C++-only host state, never exposed to Python. Set by the loader at materialization. ──
//
// The capability owns its own identity and enable flag: they are read once under the GIL, live
@@ -97,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<>())
@@ -5,7 +5,6 @@
#include "IPrinterAgent.hpp"
#include <functional>
#include <memory>
#include <string>
@@ -30,7 +29,7 @@ public:
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 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;
@@ -51,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
@@ -71,9 +71,9 @@ 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, std::string tray_id, int sequence_id, bool lan_mode) override
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, tray_id, sequence_id, lan_mode);
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