feat: initial draft of lifecycle events API for plugins

This commit is contained in:
Ian Chua
2026-08-19 19:20:31 +08:00
parent 542cd18d19
commit dfd3444ae7
21 changed files with 545 additions and 13 deletions
+43 -4
View File
@@ -7,6 +7,7 @@
#include "I18N.hpp" #include "I18N.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include "Exception.hpp" #include "Exception.hpp"
#include "LifecycleEvents.hpp"
#include "ExtrusionEntity.hpp" #include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp" #include "EdgeGrid.hpp"
#include "Geometry/ConvexHull.hpp" #include "Geometry/ConvexHull.hpp"
@@ -2446,6 +2447,13 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_writer.set_is_bbl_machine(print->is_BBL_printer()); m_writer.set_is_bbl_machine(print->is_BBL_printer());
print->set_started(psGCodeExport); print->set_started(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = path;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
// check if any custom gcode contains keywords used by the gcode processor to // check if any custom gcode contains keywords used by the gcode processor to
// produce time estimation and gcode toolpaths // produce time estimation and gcode toolpaths
std::vector<std::pair<std::string, std::string>> validation_res = DoExport::validate_custom_gcode(*print); std::vector<std::pair<std::string, std::string>> validation_res = DoExport::validate_custom_gcode(*print);
@@ -2481,12 +2489,22 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_processor.set_print(print); m_processor.set_print(print);
GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor);
if (! file.is_open()) { 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)) { if (!fs::exists(folder)) {
//fs::create_directory(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 = path;
ctx.code = LifecycleEvtCode::Error;
ctx.msg = err_msg;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(err_msg);
} }
try { try {
@@ -2497,11 +2515,18 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
boost::nowide::remove(path_tmp.c_str()); boost::nowide::remove(path_tmp.c_str());
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); 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. // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
// Close and remove the file. // Close and remove the file.
file.close(); file.close();
boost::nowide::remove(path_tmp.c_str()); boost::nowide::remove(path_tmp.c_str());
{
LifecycleEventContext ctx;
ctx.name = path;
ctx.code = LifecycleEvtCode::Error;
ctx.msg = ex.what();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw; throw;
} }
file.close(); file.close();
@@ -2607,6 +2632,13 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
std::error_code ret = rename_file(path_tmp, path); std::error_code ret = rename_file(path_tmp, path);
if (ret) { if (ret) {
{
LifecycleEventContext ctx;
ctx.name = path;
ctx.code = LifecycleEvtCode::Error;
ctx.msg = "Failed to rename the output G-code file: " + ret.message();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError( throw Slic3r::RuntimeError(
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' + 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'); "Is " + path_tmp + " locked?" + '\n');
@@ -2618,6 +2650,13 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
print->set_done(psGCodeExport); print->set_done(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = path;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
// Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT / // 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 // M486), which is driven by exclude_object for every printer
if(result != nullptr) if(result != nullptr)
+135
View File
@@ -0,0 +1,135 @@
#pragma once
// LifecycleEvents.hpp
// --------------------
// Application lifecycle events (project, slicing, plate editing, preset, printer connection
// 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 <string>
#include <utility>
namespace Slic3r
{
enum class LifecycleEvent {
// Project (3mf)
NewProject,
ProjectOpened,
ProjectBeforeSave,
ProjectAfterSave,
ProjectClosed,
// Slicing pipeline
SliceStarted,
SliceGeometryFinished,
GCodeExportStarted,
GCodeExportFinished,
SlicingJobComplete,
// Plate/model editing
ObjectAdded,
ObjectDeleted,
ObjectTransformed,
// Preset
PresetSelected,
PresetSaved,
// Printer/device
PrintStateChanged,
DeviceOnlineChanged,
DeviceDiscovered,
DeviceSelected,
DeviceConnected,
DeviceDisconnected,
UploadStarted,
UploadFinished,
};
// 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;
};
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::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::PresetSelected: return "PresetSelected";
case LifecycleEvent::PresetSaved: return "PresetSaved";
case LifecycleEvent::PrintStateChanged: return "PrintStateChanged";
case LifecycleEvent::DeviceOnlineChanged: return "DeviceOnlineChanged";
case LifecycleEvent::DeviceDiscovered: return "DeviceDiscovered";
case LifecycleEvent::DeviceSelected: return "DeviceSelected";
case LifecycleEvent::DeviceConnected: return "DeviceConnected";
case LifecycleEvent::DeviceDisconnected: return "DeviceDisconnected";
case LifecycleEvent::UploadStarted: return "UploadStarted";
case LifecycleEvent::UploadFinished: return "UploadFinished";
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&)>;
inline LifecycleHookFn& lifecycle_hook_fn()
{
static LifecycleHookFn fn;
return fn;
}
inline void set_lifecycle_hook_fn(LifecycleHookFn fn) { lifecycle_hook_fn() = std::move(fn); }
inline void fire_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx)
{
if (const LifecycleHookFn& fn = lifecycle_hook_fn(); fn)
fn(event, ctx);
}
}
+10
View File
@@ -45,6 +45,7 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "libslic3r.h" #include "libslic3r.h"
#include "LifecycleEvents.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "Time.hpp" #include "Time.hpp"
#include "PlaceholderParser.hpp" #include "PlaceholderParser.hpp"
@@ -2931,6 +2932,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, // 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config. // initialize it with the edited config.
auto it = this->find_preset_internal(new_name); 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) { if (it != m_presets.end() && it->name == new_name) {
// Preset with the same name found. // Preset with the same name found.
Preset &preset = *it; Preset &preset = *it;
@@ -3038,6 +3040,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
this->get_selected_preset().save(&(parent_preset->config)); this->get_selected_preset().save(&(parent_preset->config));
else else
this->get_selected_preset().save(nullptr); 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);
}
} }
bool PresetCollection::delete_current_preset() bool PresetCollection::delete_current_preset()
+37 -1
View File
@@ -8,6 +8,7 @@
#include "Flow.hpp" #include "Flow.hpp"
#include "Geometry/ConvexHull.hpp" #include "Geometry/ConvexHull.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "LifecycleEvents.hpp"
#include "ShortestPath.hpp" #include "ShortestPath.hpp"
#include "Thread.hpp" #include "Thread.hpp"
#include "Time.hpp" #include "Time.hpp"
@@ -2252,6 +2253,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (m_objects.empty()) if (m_objects.empty())
return; return;
{
LifecycleEventContext ctx;
ctx.name = output_filename();
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
}
for (PrintObject *obj : m_objects) for (PrintObject *obj : m_objects)
obj->clear_shared_object(); obj->clear_shared_object();
@@ -2818,6 +2826,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
} }
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
{
LifecycleEventContext ctx;
ctx.code = LifecycleEvtCode::Ok;
ctx.name = output_filename();
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
}
} }
// G-code export process, running at a background thread. // G-code export process, running at a background thread.
@@ -4434,6 +4449,13 @@ void Print::set_gcode_file_invalidated()
//BBS: add gcode file preload logic //BBS: add gcode file preload logic
void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
{ {
{
LifecycleEventContext ctx;
ctx.name = file;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
try { try {
GCodeProcessor processor; GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer(); GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
@@ -4453,13 +4475,27 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
*result = std::move(processor.extract_result()); *result = std::move(processor.extract_result());
result->filament_change_sequence = filament_seq_loaded; result->filament_change_sequence = filament_seq_loaded;
result->nozzle_change_sequence = nozzle_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(); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
{
LifecycleEventContext ctx;
ctx.name = file;
ctx.code = LifecycleEvtCode::Error;
ctx.msg = ex.what();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError( throw Slic3r::RuntimeError(
std::string("Failed to process the G-code file ") + file + " from previous 3mf\n"); 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(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully")%file.c_str();
{
LifecycleEventContext ctx;
ctx.name = file;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
} }
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
+25 -2
View File
@@ -10,6 +10,7 @@
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
@@ -328,7 +329,10 @@ namespace Slic3r
obj->bind_state = "free"; obj->bind_state = "free";
obj->last_alive = Slic3r::Utils::get_current_time_utc(); 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
// DeviceOnlineChanged 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); obj->set_dev_name(dev_name);
/* if (!obj->dev_ip.empty()) { /* if (!obj->dev_ip.empty()) {
Slic3r::GUI::wxGetApp().app_config->set_str("ip_address", obj->dev_id, obj->dev_ip); Slic3r::GUI::wxGetApp().app_config->set_str("ip_address", obj->dev_id, obj->dev_ip);
@@ -345,6 +349,10 @@ namespace Slic3r
obj->bind_sec_link = sec_link; obj->bind_sec_link = sec_link;
obj->dev_connection_name = connection_name; obj->dev_connection_name = connection_name;
obj->bind_ssdp_version = ssdp_version; 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 DeviceOnlineChanged when its reachability actually changes.
obj->m_is_online = true; obj->m_is_online = true;
//load access code //load access code
@@ -361,6 +369,15 @@ namespace Slic3r
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " New Machine, dev_id= " << dev_id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " New Machine, dev_id= " << dev_id
<< ", ip = " << dev_ip <<", printer_name = " << dev_name << ", ip = " << dev_ip <<", printer_name = " << dev_name
<< ", con_type= " << connect_type <<", signal= " << printer_signal << ", bind_state= " << bind_state; << ", 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); update_local_machine(*obj);
} }
@@ -935,8 +952,14 @@ namespace Slic3r
} }
void DeviceManager::OnSelectedMachineChanged(const std::string& /*pre_dev_id*/, 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()) { if (MachineObject* obj_ = get_selected_machine()) {
GUI::wxGetApp().sidebar().update_sync_status(obj_); GUI::wxGetApp().sidebar().update_sync_status(obj_);
if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription) if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription)
+27 -1
View File
@@ -3,6 +3,7 @@
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "GuiColor.hpp" #include "GuiColor.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
@@ -2591,7 +2592,15 @@ void MachineObject::reset()
void MachineObject::set_print_state(std::string status) void MachineObject::set_print_state(std::string status)
{ {
const bool changed = (print_status != status);
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);
}
} }
int MachineObject::connect(bool use_openssl) int MachineObject::connect(bool use_openssl)
@@ -2613,6 +2622,10 @@ int MachineObject::connect(bool use_openssl)
int MachineObject::disconnect() int MachineObject::disconnect()
{ {
if (m_agent) { if (m_agent) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
return m_agent->disconnect_printer(); return m_agent->disconnect_printer();
} }
return -1; return -1;
@@ -2643,8 +2656,16 @@ bool MachineObject::is_connecting()
void MachineObject::set_online_state(bool on_off) void MachineObject::set_online_state(bool on_off)
{ {
const bool changed = (m_is_online != on_off);
m_is_online = on_off; m_is_online = on_off;
if (!on_off) m_active_state = NotActive; 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(LifecycleEvent::DeviceOnlineChanged, ctx);
}
} }
bool MachineObject::is_info_ready(bool check_version) const bool MachineObject::is_info_ready(bool check_version) const
@@ -4566,8 +4587,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
try { try {
if (j.contains("event")) { if (j.contains("event")) {
if (j["event"].contains("event")) { if (j["event"].contains("event")) {
if (j["event"]["event"].get<std::string>() == "client.disconnected") if (j["event"]["event"].get<std::string>() == "client.disconnected") {
set_online_state(false); set_online_state(false);
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
}
else if (j["event"]["event"].get<std::string>() == "client.connected") else if (j["event"]["event"].get<std::string>() == "client.connected")
set_online_state(true); set_online_state(true);
} }
+28 -3
View File
@@ -39,6 +39,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/MacDarkMode.hpp" #include "slic3r/Utils/MacDarkMode.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <slic3r/GUI/GUI_Utils.hpp> #include <slic3r/GUI/GUI_Utils.hpp>
@@ -5061,8 +5062,16 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
//BBS: nofity object list to update //BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); 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)); post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
}
// BBS: support wipe-tower for multi-plates // BBS: support wipe-tower for multi-plates
for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) { for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) {
@@ -5183,8 +5192,16 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
//BBS: nofity object list to update //BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); 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)); post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
}
m_dirty = true; m_dirty = true;
} }
@@ -5275,8 +5292,16 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
//BBS: notify object info update //BBS: notify object info update
wxGetApp().plater()->show_object_info(); 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)); post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
}
m_dirty = true; m_dirty = true;
} }
+22
View File
@@ -2169,6 +2169,11 @@ void GUI_App::init_networking_callbacks()
obj->command_get_access_code(); obj->command_get_access_code();
if (m_agent) if (m_agent)
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer()); m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
LifecycleEventContext ctx;
ctx.name = obj->get_dev_id();
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceConnected, ctx);
} }
}); });
}); });
@@ -2207,6 +2212,11 @@ void GUI_App::init_networking_callbacks()
obj->command_get_version(); obj->command_get_version();
event.SetInt(0); event.SetInt(0);
event.SetString(obj->get_dev_id()); event.SetString(obj->get_dev_id());
LifecycleEventContext ctx;
ctx.name = obj->get_dev_id();
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceConnected, ctx);
} else if (state == ConnectStatus::ConnectStatusFailed) { } else if (state == ConnectStatus::ConnectStatusFailed) {
// Orca: only update status if same device id // Orca: only update status if same device id
if (m_device_manager->selected_machine != dev_id) return; if (m_device_manager->selected_machine != dev_id) return;
@@ -2222,10 +2232,22 @@ void GUI_App::init_networking_callbacks()
wxGetApp().show_dialog(text); wxGetApp().show_dialog(text);
} }
event.SetInt(-1); event.SetInt(-1);
{
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
}
} else if (state == ConnectStatus::ConnectStatusLost) { } else if (state == ConnectStatus::ConnectStatusLost) {
m_device_manager->set_selected_machine(""); m_device_manager->set_selected_machine("");
event.SetInt(-1); event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost"; BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost";
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
} else { } else {
event.SetInt(-1); event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state; BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state;
+15
View File
@@ -9,6 +9,7 @@
#include "BitmapComboBox.hpp" #include "BitmapComboBox.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "OptionsGroup.hpp" #include "OptionsGroup.hpp"
#include "Tab.hpp" #include "Tab.hpp"
@@ -3530,7 +3531,14 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx)
obj->delete_connectors(); obj->delete_connectors();
if (obj->volumes.empty() || !obj->has_solid_mesh()) { if (obj->volumes.empty() || !obj->has_solid_mesh()) {
const std::string deleted_obj_name = obj->name;
model.delete_object(idx); 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)); m_objects_model->Delete(m_objects_model->GetItemById(idx));
continue; continue;
} }
@@ -4099,6 +4107,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()); const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut());
Expand(m_objects_model->GetParent(item)); 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) if (!do_info_update)
return; return;
+8
View File
@@ -13,6 +13,7 @@
#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/format.hpp" #include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp" #include "libnest2d/common.hpp"
@@ -699,6 +700,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; 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(); m_plater->update();
// BBS // BBS
//wxGetApp().obj_manipul()->set_dirty(); //wxGetApp().obj_manipul()->set_dirty();
+8
View File
@@ -6,6 +6,7 @@
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp" #include "libnest2d/common.hpp"
#include <numeric> #include <numeric>
@@ -347,6 +348,13 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
m_plater->arrange(); m_plater->arrange();
} }
m_plater->update(); 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(); m_plater->mark_plate_toolbar_image_dirty();
+7
View File
@@ -5,6 +5,7 @@
#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
@@ -254,6 +255,12 @@ void OrientJob::finalize(bool canceled, std::exception_ptr &eptr)
mesh.apply(); 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(); m_plater->update();
+58
View File
@@ -8375,7 +8375,14 @@ void Plater::priv::remove(size_t obj_idx)
view3D->enable_layers_editing(false); view3D->enable_layers_editing(false);
m_worker.cancel_all(); 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); 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 //BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1); partplate_list.notify_instance_removed(obj_idx, -1);
update(); update();
@@ -8408,7 +8415,14 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia
if (obj->is_cut()) if (obj->is_cut())
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
std::string obj_name = obj->name;
model.delete_object(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 //BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1); partplate_list.notify_instance_removed(obj_idx, -1);
@@ -8458,6 +8472,14 @@ void Plater::priv::reset(bool apply_presets_change)
clear_warnings(); 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);
}
set_project_filename(""); set_project_filename("");
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " call set_project_filename: empty"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " call set_project_filename: empty";
@@ -10868,11 +10890,15 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
notification_manager->set_slicing_progress_export_possible(); notification_manager->set_slicing_progress_export_possible();
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again. // 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->background_process.reset_export();
// This bool stops showing export finished notification even when process_completed_with_error is false // This bool stops showing export finished notification even when process_completed_with_error is false
bool has_error = false; bool has_error = false;
std::string lifecycle_error_msg;
if (evt.error()) { if (evt.error()) {
auto message = evt.format_error_message(); auto message = evt.format_error_message();
lifecycle_error_msg = message.first;
if (evt.critical_error()) { if (evt.critical_error()) {
if (q->m_tracking_popup_menu) { if (q->m_tracking_popup_menu) {
// We don't want to pop-up a message box when tracking a pop-up menu. // We don't want to pop-up a message box when tracking a pop-up menu.
@@ -10910,6 +10936,14 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
is_finished = true; 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 //BBS: set the current plater's slice result to valid
if (!this->background_process.empty()) if (!this->background_process.empty())
this->background_process.get_current_plate()->update_slice_result_valid_state(evt.success()); this->background_process.get_current_plate()->update_slice_result_valid_state(evt.success());
@@ -13068,6 +13102,11 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
//get_partplate_list().reinit(); //get_partplate_list().reinit();
//get_partplate_list().update_slice_context_to_current_plate(p->background_process); //get_partplate_list().update_slice_context_to_current_plate(p->background_process);
//p->preview->update_gcode_result(p->partplate_list.get_current_slice_result()); //p->preview->update_gcode_result(p->partplate_list.get_current_slice_result());
if (!silent) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::NewProject, ctx);
}
reset(transfer_preset_changes); reset(transfer_preset_changes);
reset_project_dirty_after_save(); reset_project_dirty_after_save();
reset_project_dirty_initial_presets(); reset_project_dirty_initial_presets();
@@ -13199,6 +13238,13 @@ void Plater::load_project(wxString const& filename2,
p->set_project_name(_L("Untitled")); 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 { } else {
if (using_exported_file()) { if (using_exported_file()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename;
@@ -13261,11 +13307,23 @@ int Plater::save_project(bool saveAs)
if (full_pathnames) { if (full_pathnames) {
save_strategy = save_strategy | SaveStrategy::FullPathSources; 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) { 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."), 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(); _L("Save project"), wxOK | wxICON_WARNING).ShowModal();
return wxID_CANCEL; 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); Slic3r::remove_backup(model(), false);
+15
View File
@@ -36,6 +36,7 @@
#include "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp" #include "slic3r/plugin/PluginConfig.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "format.hpp" #include "format.hpp"
@@ -6900,6 +6901,20 @@ bool Tab::select_preset(
} }
load_current_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) { if (delete_third_printer) {
wxGetApp().CallAfter([filament_presets, process_presets]() { wxGetApp().CallAfter([filament_presets, process_presets]() {
+18
View File
@@ -23,6 +23,7 @@
#include "CrealityPrint.hpp" #include "CrealityPrint.hpp"
#include "../GUI/PrintHostDialogs.hpp" #include "../GUI/PrintHostDialogs.hpp"
#include "../GUI/MainFrame.hpp" #include "../GUI/MainFrame.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Obico.hpp" #include "Obico.hpp"
#include "Flashforge.hpp" #include "Flashforge.hpp"
#include "SimplyPrint.hpp" #include "SimplyPrint.hpp"
@@ -358,12 +359,29 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{ {
emit_progress(0); // Indicate the upload is starting 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), 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](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); }, [this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); } [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) { if (success) {
emit_progress(100); emit_progress(100);
if (the_job.switch_to_device_tab) { if (the_job.switch_to_device_tab) {
+13
View File
@@ -7,6 +7,7 @@
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "libslic3r/Exception.hpp" #include "libslic3r/Exception.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r_version.h" #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 // Print::process() fires this hook at each pipeline seam on the slicing worker
// thread; here we run the picker-selected SlicingPipeline capabilities. Per // thread; here we run the picker-selected SlicingPipeline capabilities. Per
// capability we acquire the GIL, honor cancellation, and convert a plugin // capability we acquire the GIL, honor cancellation, and convert a plugin
@@ -122,12 +133,14 @@ void install()
{ {
install_capability_resolver(); install_capability_resolver();
install_slicing_pipeline_hook(); install_slicing_pipeline_hook();
install_lifecycle_event_hook();
} }
void uninstall() void uninstall()
{ {
ConfigBase::set_resolve_capability_fn(nullptr); ConfigBase::set_resolve_capability_fn(nullptr);
Print::set_slicing_pipeline_hook_fn(nullptr); Print::set_slicing_pipeline_hook_fn(nullptr);
set_lifecycle_hook_fn(nullptr);
} }
} // namespace Slic3r::plugin_hooks } // namespace Slic3r::plugin_hooks
+21
View File
@@ -1,5 +1,6 @@
#include "PluginManager.hpp" #include "PluginManager.hpp"
#include <exception>
#include <libslic3r/Utils.hpp> #include <libslic3r/Utils.hpp>
#include <memory> #include <memory>
#include <pybind11/embed.h> #include <pybind11/embed.h>
@@ -1987,4 +1988,24 @@ ExecutionResult PluginManager::run_script_capability(const std::string& plugin_k
return result; return result;
} }
void PluginManager::dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx) {
for (const auto& cap : get_plugin_capabilities()) {
if (!cap || !cap->is_enabled()) continue;
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 } // namespace Slic3r
+3 -1
View File
@@ -8,6 +8,7 @@
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <libslic3r/Config.hpp> #include <libslic3r/Config.hpp>
#include <libslic3r/LifecycleEvents.hpp>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -20,7 +21,6 @@
#include <pybind11/embed.h> #include <pybind11/embed.h>
#include "CloudPluginService.hpp" #include "CloudPluginService.hpp"
#include "PluginFsUtils.hpp"
#include "PluginDescriptor.hpp" #include "PluginDescriptor.hpp"
#include "PluginLoader.hpp" #include "PluginLoader.hpp"
#include "PluginConfig.hpp" #include "PluginConfig.hpp"
@@ -204,6 +204,8 @@ public:
ExecutionResult run_script_capability(const std::string& plugin_key, const std::string& capability_name, std::string& error); 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: private:
PluginManager() = default; PluginManager() = default;
PluginManager(const PluginManager&) = delete; PluginManager(const PluginManager&) = delete;
+6
View File
@@ -129,6 +129,12 @@ public:
{ {
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled); ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
} }
void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) override
{
ORCA_PY_OVERRIDE_AUDITED(
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_lifecycle_event, event, ctx);
}
}; };
class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline<PluginCapabilityInterface> class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline<PluginCapabilityInterface>
+41
View File
@@ -338,6 +338,44 @@ void bind_python_api(pybind11::module_& m)
.value("FatalError", PluginResult::FatalError) .value("FatalError", PluginResult::FatalError)
.export_values(); .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("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("PresetSelected", LifecycleEvent::PresetSelected)
.value("PresetSaved", LifecycleEvent::PresetSaved)
.value("PrintStateChanged", LifecycleEvent::PrintStateChanged)
.value("DeviceOnlineChanged", LifecycleEvent::DeviceOnlineChanged)
.value("DeviceDiscovered", LifecycleEvent::DeviceDiscovered)
.value("DeviceSelected", LifecycleEvent::DeviceSelected)
.value("DeviceConnected", LifecycleEvent::DeviceConnected)
.value("DeviceDisconnected", LifecycleEvent::DeviceDisconnected)
.value("UploadStarted", LifecycleEvent::UploadStarted)
.value("UploadFinished", LifecycleEvent::UploadFinished)
.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);
py::class_<PluginContext>(m, "PluginContext", "Context shared with plugin entry points") py::class_<PluginContext>(m, "PluginContext", "Context shared with plugin entry points")
.def(py::init<>()) .def(py::init<>())
.def_readwrite("orca_version", &PluginContext::orca_version); .def_readwrite("orca_version", &PluginContext::orca_version);
@@ -362,6 +400,9 @@ void bind_python_api(pybind11::module_& m)
.def("get_type", &PluginCapabilityInterface::get_type) .def("get_type", &PluginCapabilityInterface::get_type)
.def("on_load", &PluginCapabilityInterface::on_load) .def("on_load", &PluginCapabilityInterface::on_load)
.def("on_unload", &PluginCapabilityInterface::on_unload) .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, .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" "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" "UI, returned by get_config_ui(). Every capability is configurable and appears in the\n"
@@ -10,6 +10,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <pybind11/embed.h> #include <pybind11/embed.h>
#include <libslic3r/LifecycleEvents.hpp>
namespace Slic3r { namespace Slic3r {
enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown }; enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
@@ -167,6 +169,8 @@ public:
virtual void on_unload() {} virtual void on_unload() {}
virtual void on_cancelled() {} 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. ── // ── 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 // The capability owns its own identity and enable flag: they are read once under the GIL, live