mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-26 11:27:41 +00:00
Compare commits
2 Commits
feat/plugi
...
feat/plate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e0f9cdfb | ||
|
|
790a010615 |
@@ -631,7 +631,7 @@ void parse_metadata_rfc822(const std::string& content,
|
||||
bool is_ignored_plugin_directory(const boost::filesystem::path& path)
|
||||
{
|
||||
const std::string name = path.filename().string();
|
||||
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR || name == PLUGIN_DATA_DIR;
|
||||
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR;
|
||||
}
|
||||
|
||||
bool is_safe_relative_path(const boost::filesystem::path& path)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <vector>
|
||||
|
||||
#define PLUGIN_SUBSCRIBED_DIR "_subscribed"
|
||||
#define PLUGIN_DATA_DIR "plugin_data"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -522,38 +522,6 @@ bool PluginManager::try_get_plugin_descriptor_for_capability(const std::string&
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string PluginManager::get_storage_dir(const std::string& plugin_key) const
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!try_get_plugin_descriptor(plugin_key, descriptor))
|
||||
throw std::runtime_error("The current plugin is not registered");
|
||||
|
||||
const fs::path base_storage_dir = fs::path(get_orca_plugins_dir()) / PLUGIN_DATA_DIR;
|
||||
|
||||
if (!descriptor.is_cloud_plugin()) {
|
||||
const fs::path local_storage_dir = base_storage_dir / plugin_key;
|
||||
fs::create_directories(local_storage_dir);
|
||||
return local_storage_dir.string();
|
||||
}
|
||||
|
||||
auto agent = m_cloud_service.get_cloud_agent();
|
||||
if (!agent)
|
||||
throw std::runtime_error("Cloud plugin storage is unavailable before networking is initialized");
|
||||
|
||||
const std::string user_id = agent->get_user_id();
|
||||
if (user_id.empty())
|
||||
throw std::runtime_error("Cloud plugin storage is unavailable without a logged-in user");
|
||||
|
||||
if (!is_valid_plugin_id(plugin_key))
|
||||
throw std::runtime_error("The current cloud plugin key is not a valid folder name");
|
||||
|
||||
const fs::path cloud_storage_dir = base_storage_dir / PLUGIN_SUBSCRIBED_DIR / user_id / plugin_key;
|
||||
fs::create_directories(cloud_storage_dir);
|
||||
return cloud_storage_dir.string();
|
||||
}
|
||||
|
||||
// ── Capability instances ────────────────────────────────────────────────────────────────────
|
||||
|
||||
std::vector<std::shared_ptr<PluginCapabilityInterface>> PluginManager::get_plugin_capabilities(const std::string& plugin_key,
|
||||
|
||||
@@ -143,10 +143,6 @@ public:
|
||||
bool try_get_plugin_descriptor_for_capability(const std::string& capability_name,
|
||||
PluginCapabilityType type,
|
||||
PluginDescriptor& out) const;
|
||||
// Per-plugin storage directory under orca_plugins/plugin_data, created if missing. Throws
|
||||
// std::runtime_error if the plugin is unregistered, the key is invalid, or (cloud plugins)
|
||||
// no user is logged in yet.
|
||||
std::string get_storage_dir(const std::string& plugin_key) const;
|
||||
|
||||
std::vector<std::shared_ptr<PluginCapabilityInterface>> get_plugin_capabilities(
|
||||
const std::string& plugin_key = "", // "" => all plugins
|
||||
|
||||
@@ -1,31 +1,9 @@
|
||||
#include "PluginHost.hpp"
|
||||
#include "PluginHostBindings.hpp"
|
||||
#include "PluginHostUi.hpp"
|
||||
#include <slic3r/plugin/PluginAuditManager.hpp>
|
||||
#include <slic3r/plugin/PluginManager.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace host_bindings {
|
||||
void register_plugin(pybind11::module_& host)
|
||||
{
|
||||
auto plugin_host = host.def_submodule("plugin", "Plugin host API");
|
||||
|
||||
plugin_host.def(
|
||||
"storage",
|
||||
[]() -> std::string {
|
||||
const std::string plugin_key = PluginAuditManager::instance().current_plugin();
|
||||
if (plugin_key.empty())
|
||||
throw std::runtime_error("plugin.storage() must be called from a plugin callback");
|
||||
|
||||
return PluginManager::instance().get_storage_dir(plugin_key);
|
||||
},
|
||||
"Return the installed folder of the current plugin.");
|
||||
}
|
||||
} // namespace host_bindings
|
||||
|
||||
void PluginHost::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
auto host = module.def_submodule("host", "Host application API");
|
||||
@@ -37,7 +15,6 @@ void PluginHost::RegisterBindings(pybind11::module_& module)
|
||||
host_bindings::register_presets(host);
|
||||
host_bindings::register_model(host);
|
||||
host_bindings::register_app(host);
|
||||
host_bindings::register_plugin(host);
|
||||
|
||||
// UI: native dialogs and interactive HTML windows for plugins.
|
||||
PluginHostUi::RegisterBindings(host);
|
||||
|
||||
@@ -12,5 +12,5 @@ void register_presets(pybind11::module_& host); // PluginHostPresets.cpp
|
||||
void register_model(pybind11::module_& host); // PluginHostModel.cpp
|
||||
void register_app(pybind11::module_& host); // PluginHostApp.cpp
|
||||
void register_slicing(pybind11::module_& host); // PluginHostSlicing.cpp
|
||||
void register_plugin(pybind11::module_& host); // PluginHost.cpp
|
||||
|
||||
} // namespace Slic3r::host_bindings
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <slic3r/GUI/MsgDialog.hpp>
|
||||
#include <slic3r/GUI/PluginProgressDialog.hpp>
|
||||
#include <slic3r/GUI/PluginWebDialog.hpp>
|
||||
#include <slic3r/GUI/NotificationManager.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <pybind11/pybind11.h>
|
||||
@@ -19,6 +20,7 @@
|
||||
#include <wx/defs.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
@@ -44,16 +46,20 @@ namespace {
|
||||
struct GilSafeCallable
|
||||
{
|
||||
py::object fn;
|
||||
std::atomic_bool active{true};
|
||||
explicit GilSafeCallable(py::object f) : fn(std::move(f)) {}
|
||||
~GilSafeCallable()
|
||||
void disable()
|
||||
{
|
||||
if (fn) {
|
||||
active.store(false, std::memory_order_release);
|
||||
PythonGILState gil;
|
||||
if (gil)
|
||||
fn = py::object();
|
||||
else
|
||||
(void) fn.release();
|
||||
}
|
||||
~GilSafeCallable()
|
||||
{
|
||||
disable();
|
||||
}
|
||||
};
|
||||
using CallablePtr = std::shared_ptr<GilSafeCallable>;
|
||||
@@ -166,11 +172,34 @@ public:
|
||||
}
|
||||
return out;
|
||||
}
|
||||
void bind_callback(const CallablePtr& callback, const std::string& plugin_key)
|
||||
{
|
||||
if (!callback)
|
||||
return;
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
m_callbacks[plugin_key].push_back(callback);
|
||||
}
|
||||
std::vector<CallablePtr> take_callbacks_for_plugin(const std::string& plugin_key)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
auto it = m_callbacks.find(plugin_key);
|
||||
if (it == m_callbacks.end())
|
||||
return {};
|
||||
std::vector<CallablePtr> callbacks;
|
||||
callbacks.reserve(it->second.size());
|
||||
for (const std::weak_ptr<GilSafeCallable>& weak_callback : it->second) {
|
||||
if (auto callback = weak_callback.lock())
|
||||
callbacks.push_back(std::move(callback));
|
||||
}
|
||||
m_callbacks.erase(it);
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mtx;
|
||||
std::unordered_map<int, wxWindow*> m_resources;
|
||||
std::unordered_map<int, std::string> m_owners;
|
||||
std::unordered_map<std::string, std::vector<std::weak_ptr<GilSafeCallable>>> m_callbacks;
|
||||
int m_next_id{1};
|
||||
};
|
||||
|
||||
@@ -448,6 +477,46 @@ void progress_close(int id)
|
||||
});
|
||||
}
|
||||
|
||||
void plater_notification(NotificationManager::NotificationLevel notification_level, const std::string& text,
|
||||
const std::string& hypertext, py::object on_click)
|
||||
{
|
||||
const std::string plugin_key = PluginAuditManager::instance().current_plugin();
|
||||
CallablePtr holder = make_holder(std::move(on_click));
|
||||
if (holder)
|
||||
UiRegistry::instance().bind_callback(holder, plugin_key);
|
||||
|
||||
std::function<bool(wxEvtHandler*)> callback;
|
||||
if (holder) {
|
||||
callback = [holder](wxEvtHandler*) -> bool {
|
||||
if (!holder->active.load(std::memory_order_acquire))
|
||||
return false;
|
||||
|
||||
PythonGILState gil;
|
||||
if (!gil)
|
||||
return false;
|
||||
try {
|
||||
py::object result = holder->fn();
|
||||
return result.is_none() || result.cast<bool>();
|
||||
} catch (py::error_already_set& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised: " << e.what();
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised: " << e.what();
|
||||
return false;
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised an unknown exception";
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
run_on_ui_blocking([notification_level, text, hypertext, callback = std::move(callback)]() mutable {
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification, notification_level, text,
|
||||
hypertext, std::move(callback));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PluginHostUi::RegisterBindings(pybind11::module_& host)
|
||||
@@ -530,6 +599,23 @@ void PluginHostUi::RegisterBindings(pybind11::module_& host)
|
||||
ui.def("create_progress_dialog", &ui_create_progress_dialog, py::arg("title"), py::arg("message"),
|
||||
py::arg("maximum") = 100, py::arg("style") = wxPD_APP_MODAL | wxPD_AUTO_HIDE,
|
||||
"Create a native progress dialog and return a ProgressDialog handle.");
|
||||
|
||||
py::enum_<NotificationManager::NotificationLevel>(ui, "NotificationLevel")
|
||||
.value("ProgressBarNotificationLevel", NotificationManager::NotificationLevel::ProgressBarNotificationLevel)
|
||||
.value("HintNotificationLevel", NotificationManager::NotificationLevel::HintNotificationLevel)
|
||||
.value("RegularNotificationLevel", NotificationManager::NotificationLevel::RegularNotificationLevel)
|
||||
.value("PrintInfoNotificationLevel", NotificationManager::NotificationLevel::PrintInfoNotificationLevel)
|
||||
.value("PrintInfoShortNotificationLevel", NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel)
|
||||
.value("ImportantNotificationLevel", NotificationManager::NotificationLevel::ImportantNotificationLevel)
|
||||
.value("WarningNotificationLevel", NotificationManager::NotificationLevel::WarningNotificationLevel)
|
||||
.value("SeriousWarningNotificationLevel", NotificationManager::NotificationLevel::SeriousWarningNotificationLevel)
|
||||
.value("ErrorNotificationLevel", NotificationManager::NotificationLevel::ErrorNotificationLevel)
|
||||
.export_values();
|
||||
|
||||
ui.def("push_notification", &plater_notification, py::arg("notification_level"), py::arg("text"),
|
||||
py::arg("hyper_text") = "", py::arg("on_click") = py::none(),
|
||||
"Push a plater notification. hyper_text is an underlined label; on_click() is called when it is clicked "
|
||||
"and may return True to close the notification.");
|
||||
}
|
||||
|
||||
void PluginHostUi::close_windows_for_plugin(const std::string& plugin_key)
|
||||
@@ -538,6 +624,9 @@ void PluginHostUi::close_windows_for_plugin(const std::string& plugin_key)
|
||||
return;
|
||||
|
||||
auto teardown = [plugin_key]() {
|
||||
for (auto& callback : UiRegistry::instance().take_callbacks_for_plugin(plugin_key))
|
||||
callback->disable();
|
||||
|
||||
// Destroy() bypasses wxEVT_CLOSE, so the plugin's on_close is not fired on
|
||||
// forced teardown (intended); the resource destructor still cleans the registry.
|
||||
for (auto* window : UiRegistry::instance().take_for_plugin(plugin_key)) {
|
||||
|
||||
Reference in New Issue
Block a user