Compare commits

...
29 changed files with 853 additions and 30 deletions
+45 -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,14 @@ 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 = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
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 +2490,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 = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + err_msg;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(err_msg);
} }
try { try {
@@ -2497,11 +2516,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 = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + ex.what();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw; throw;
} }
file.close(); file.close();
@@ -2607,6 +2633,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 = 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();
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 +2651,14 @@ 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 = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
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)
+181
View File
@@ -0,0 +1,181 @@
#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 <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,
DeviceOnlineChanged,
DeviceDiscovered,
DeviceSelected,
DeviceConnected,
DeviceDisconnected,
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;
};
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::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";
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&)>;
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
@@ -47,6 +47,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"
@@ -2947,6 +2948,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;
@@ -3054,6 +3056,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()
+39 -1
View File
@@ -9,6 +9,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"
@@ -2284,6 +2285,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 = std::to_string(m_model.id().id);
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();
@@ -2896,6 +2904,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.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
}
} }
// G-code export process, running at a background thread. // G-code export process, running at a background thread.
@@ -4518,6 +4533,14 @@ 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 = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
try { try {
GCodeProcessor processor; GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer(); GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
@@ -4537,13 +4560,28 @@ 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 = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = file + "\n" + 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 = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
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 "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
@@ -355,7 +356,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);
@@ -373,6 +377,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
@@ -389,6 +397,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);
} }
@@ -984,8 +1001,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 "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "GuiColor.hpp" #include "GuiColor.hpp"
@@ -2622,7 +2623,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)
@@ -2644,6 +2653,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;
@@ -2674,8 +2687,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
@@ -4597,8 +4618,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>
@@ -5070,8 +5071,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++) {
@@ -5192,8 +5201,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;
} }
@@ -5284,8 +5301,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;
+39 -1
View File
@@ -1,5 +1,6 @@
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "GUI_ObjectList.hpp" #include "GUI_ObjectList.hpp"
#include "GUI_Factories.hpp" #include "GUI_Factories.hpp"
//#include "GUI_ObjectLayers.hpp" //#include "GUI_ObjectLayers.hpp"
@@ -9,6 +10,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"
@@ -1199,17 +1201,39 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
if (m_objects_model->GetItemType(item) & itObject) { if (m_objects_model->GetItemType(item) & itObject) {
std::string name = m_objects_model->GetName(item).ToUTF8().data(); std::string name = m_objects_model->GetName(item).ToUTF8().data();
if (obj->name != name) { if (obj->name != name) {
const std::string previous_name = obj->name;
obj->name = name; obj->name = name;
// if object has just one volume, rename this volume too // if object has just one volume, rename this volume too
if (obj->volumes.size() == 1) if (obj->volumes.size() == 1)
obj->volumes[0]->name = obj->name; obj->volumes[0]->name = obj->name;
Slic3r::save_object_mesh(*obj); 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; return;
} }
if (volume_id < 0) 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 void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
@@ -3548,7 +3572,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;
} }
@@ -4117,6 +4148,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();
+22
View File
@@ -1,4 +1,5 @@
#include "PrintJob.hpp" #include "PrintJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/MTUtils.hpp" #include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
@@ -133,6 +134,13 @@ wxString PrintJob::get_http_error_msg(unsigned int status, std::string body)
void PrintJob::process(Ctl &ctl) void PrintJob::process(Ctl &ctl)
{ {
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;
/* display info */ /* display info */
std::string msg; std::string msg;
wxString error_str; wxString error_str;
@@ -689,6 +697,7 @@ void PrintJob::process(Ctl &ctl)
} }
wxQueueEvent(m_plater, evt); wxQueueEvent(m_plater, evt);
m_job_finished = true; m_job_finished = true;
m_lifecycle_success = true;
} }
} }
@@ -701,6 +710,19 @@ void PrintJob::finalize(bool canceled, std::exception_ptr &eptr) {
eptr = std::current_exception(); 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) if (canceled || eptr)
return; return;
} }
+3
View File
@@ -43,6 +43,9 @@ class PrintJob : public Job
std::function<void()> m_success_fun{nullptr}; std::function<void()> m_success_fun{nullptr};
std::string m_dev_id; std::string m_dev_id;
bool m_job_finished{ false }; 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; int m_print_job_completed_id = 0;
wxString m_completed_evt_data; wxString m_completed_evt_data;
std::function<void()> m_enter_ip_address_fun_fail{ nullptr }; std::function<void()> m_enter_ip_address_fun_fail{ nullptr };
+21
View File
@@ -1,4 +1,5 @@
#include "SendJob.hpp" #include "SendJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/MTUtils.hpp" #include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
@@ -146,6 +147,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(); 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); PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx);
@@ -423,6 +431,19 @@ void SendJob::finalize(bool canceled, std::exception_ptr &eptr)
eptr = std::current_exception(); 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) if (canceled || eptr)
return; return;
} }
+2
View File
@@ -22,6 +22,8 @@ class SendJob : public Job
PrintPrepareData job_data; PrintPrepareData job_data;
std::string m_dev_id; std::string m_dev_id;
bool m_job_finished{ false }; bool m_job_finished{ false };
bool m_lifecycle_started{ false };
bool m_lifecycle_finished{ false };
int m_print_job_completed_id = 0; int m_print_job_completed_id = 0;
bool m_is_check_mode{false}; bool m_is_check_mode{false};
bool m_check_and_continue{false}; bool m_check_and_continue{false};
+34 -1
View File
@@ -26,6 +26,7 @@
#include "libslic3r/Tesselate.hpp" #include "libslic3r/Tesselate.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
@@ -2628,11 +2629,20 @@ void PartPlate::set_plate_name(const std::string& name)
if (boost::equals(m_name, name)) if (boost::equals(m_name, name))
return; return;
const std::string previous_name = m_name;
m_name = name; m_name = name;
if (m_print != nullptr) if (m_print != nullptr)
m_print->set_plate_name(name); m_print->set_plate_name(name);
invalidate_plate_name_texture(); 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 //get the print's object, result and index
@@ -4737,9 +4747,16 @@ int PartPlateList::create_plate(bool adjust_position)
if (m_plater) { if (m_plater) {
// In GUI mode // 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; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":created a new plate %1%") % new_index;
return new_index; return new_index;
} }
@@ -4838,6 +4855,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(); BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":plate %1%, has an invalid index %2%") % index % plate->get_index();
return -1; return -1;
} }
const std::string plate_name = plate->get_plate_name();
if (m_plater) { if (m_plater) {
// In GUI mode // In GUI mode
@@ -4920,6 +4938,13 @@ int PartPlateList::delete_plate(int index)
delete plate; 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(). // FIX: context of BackgroundSliceProcess and gcode preview need to be updated before ObjectList::reload_all_plates().
#if 0 #if 0
if (m_plater != nullptr) { if (m_plater != nullptr) {
@@ -5028,6 +5053,7 @@ int PartPlateList::select_plate(int index)
if (m_plate_list.empty() || index >= m_plate_list.size()) { if (m_plate_list.empty() || index >= m_plate_list.size()) {
return -1; return -1;
} }
const int previous_index = m_current_plate;
// BBS: erase unnecessary snapshot // BBS: erase unnecessary snapshot
if (get_curr_plate_index() != index && m_intialized) { if (get_curr_plate_index() != index && m_intialized) {
@@ -5054,6 +5080,13 @@ int PartPlateList::select_plate(int index)
//wxQueueEvent(m_plater, new SimpleEvent(EVT_GLCANVAS_PLATE_SELECT)); //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; return 0;
} }
+106 -13
View File
@@ -61,6 +61,7 @@
#include <wx/aui/aui.h> #include <wx/aui/aui.h>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/DRC.hpp" #include "libslic3r/Format/DRC.hpp"
#include "libslic3r/Format/STEP.hpp" #include "libslic3r/Format/STEP.hpp"
@@ -9913,7 +9914,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();
@@ -9946,7 +9954,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);
@@ -9992,10 +10007,23 @@ void Plater::priv::delete_all_objects_from_model()
void Plater::priv::reset(bool apply_presets_change) 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); Plater::TakeSnapshot snapshot(q, _u8L("Reset Project"), UndoRedo::SnapshotType::ProjectSeparator);
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";
@@ -12408,11 +12436,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.
@@ -12450,6 +12482,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());
@@ -14979,21 +15019,33 @@ 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());
reset(transfer_preset_changes); if (!silent) {
reset_project_dirty_after_save(); Slic3r::LifecycleEventContext ctx;
reset_project_dirty_initial_presets(); ctx.code = Slic3r::LifecycleEvtCode::Ok;
wxGetApp().update_saved_preset_from_current_preset(); Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::NewProject, ctx);
update_project_dirty_from_presets(); }
{
// 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 reset(transfer_preset_changes);
p->project.reset(); reset_project_dirty_after_save();
//set project name reset_project_dirty_initial_presets();
if (project_name.empty()) wxGetApp().update_saved_preset_from_current_preset();
p->set_project_name(_L("Untitled")); update_project_dirty_from_presets();
else
p->set_project_name(project_name);
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 m;
model().load_from(m); // new id avoid same path name model().load_from(m); // new id avoid same path name
@@ -15110,6 +15162,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;
@@ -15172,11 +15231,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);
@@ -20314,6 +20385,14 @@ void Plater::changed_object(ModelObject &object){
// Check outside bed // Check outside bed
get_current_canvas3D()->requires_check_outside_state(); 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) void Plater::changed_object(int obj_idx)
@@ -20350,6 +20429,20 @@ void Plater::changed_objects(const std::vector<size_t>& object_idxs)
// update print // update print
this->p->schedule_background_process(); 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*/) void Plater::schedule_background_process(bool schedule/* = true*/)
+46 -1
View File
@@ -6,6 +6,7 @@
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
@@ -17,13 +18,23 @@ namespace GUI {
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty) void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{ {
const bool was_dirty = is_dirty();
m_plater_dirty = dirty; m_plater_dirty = dirty;
notify_dirty_change(was_dirty, "undo_redo");
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized()) if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title(); 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() void ProjectDirtyStateManager::update_from_presets()
{ {
const bool was_dirty = is_dirty();
m_presets_dirty = false; m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new // check switching of the presets only for exist/loaded project, but not for new
GUI_App &app = wxGetApp(); GUI_App &app = wxGetApp();
@@ -45,18 +56,53 @@ void ProjectDirtyStateManager::update_from_presets()
} }
m_presets_dirty |= app.has_unsaved_preset_changes(); m_presets_dirty |= app.has_unsaved_preset_changes();
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config; m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
notify_dirty_change(was_dirty, "presets");
app.mainframe->update_title(); app.mainframe->update_title();
} }
void ProjectDirtyStateManager::reset_after_save() void ProjectDirtyStateManager::reset_after_save()
{ {
const bool was_dirty = is_dirty();
this->reset_initial_presets(); this->reset_initial_presets();
m_plater_dirty = false; m_plater_dirty = false;
m_presets_dirty = false; m_presets_dirty = false;
m_project_config_dirty = false; m_project_config_dirty = false;
notify_dirty_change(was_dirty, "save");
wxGetApp().mainframe->update_title(); 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() void ProjectDirtyStateManager::reset_initial_presets()
{ {
m_initial_presets.fill(std::string{}); m_initial_presets.fill(std::string{});
@@ -168,4 +214,3 @@ void ProjectDirtyStateManager::render_debug_window() const
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r
+22 -1
View File
@@ -14,21 +14,42 @@ public:
void reset_after_save(); void reset_after_save();
void reset_initial_presets(); 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_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; }
bool is_presets_dirty() const { return 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 #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const; void render_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
private: 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? // Does the Undo / Redo stack indicate the project is dirty?
bool m_plater_dirty { false }; bool m_plater_dirty { false };
// Do the presets indicate the project is dirty? // Do the presets indicate the project is dirty?
bool m_presets_dirty { false }; bool m_presets_dirty { false };
// Is the project config dirty? // Is the project config dirty?
bool m_project_config_dirty { false }; 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. // Keeps track of preset names selected at the time of last project save.
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets; std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
DynamicPrintConfig m_initial_project_config; DynamicPrintConfig m_initial_project_config;
+15
View File
@@ -37,6 +37,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"
@@ -6920,6 +6921,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
@@ -1,6 +1,7 @@
#include "TaskManager.hpp" #include "TaskManager.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
@@ -210,6 +211,13 @@ int TaskManager::schedule(TaskStateInfo* task)
assert(task->state() == TaskState::TS_PENDING); assert(task->state() == TaskState::TS_PENDING);
task->set_state(TaskState::TS_SENDING); task->set_state(TaskState::TS_SENDING);
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);
BOOST_LOG_TRIVIAL(trace) << "task_manager: schedule a task to dev_id = " << task->params().dev_id; BOOST_LOG_TRIVIAL(trace) << "task_manager: schedule a task to dev_id = " << task->params().dev_id;
boost::thread* new_sending_thread = new boost::thread(); boost::thread* new_sending_thread = new boost::thread();
*new_sending_thread = Slic3r::create_thread( *new_sending_thread = Slic3r::create_thread(
@@ -238,6 +246,16 @@ int TaskManager::schedule(TaskStateInfo* task)
} }
} }
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 */ /* remove from sending task list */
m_scedule_mutex.lock(); m_scedule_mutex.lock();
auto it = std::find(m_scedule_list.begin(), m_scedule_list.end(), task); auto it = std::find(m_scedule_list.begin(), m_scedule_list.end(), task);
+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>
+59
View File
@@ -339,6 +339,62 @@ 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("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("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)
.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") 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);
@@ -363,6 +419,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, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown }; 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_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