Fix/impl refactor (#14776)

* fix: impl refactor

* fix: unload/load python module, race conditions, freezes

* remove dead code

* remove extra hook

* remove more dead code

* fix gil run script
This commit is contained in:
Ian Chua
2026-07-15 20:12:51 +08:00
committed by GitHub
parent 1551898846
commit 62afea225a
35 changed files with 4162 additions and 3373 deletions
+389 -290
View File
@@ -15,17 +15,77 @@
#include <cstring>
#include <ctime>
#include <iomanip>
#include <mutex>
#include <sstream>
#include <vector>
namespace Slic3r {
thread_local PythonInterpreter* PythonRuntimeLease::s_owner = nullptr;
thread_local unsigned int PythonRuntimeLease::s_depth = 0;
PythonRuntimeLease::PythonRuntimeLease(PythonInterpreter& interpreter)
{
if (s_owner == &interpreter) {
m_interpreter = &interpreter;
++s_depth;
return;
}
m_lock = std::shared_lock<std::shared_mutex>(interpreter.m_runtime_mutex);
if (!interpreter.m_initialized.load(std::memory_order_acquire)) {
m_lock.unlock();
return;
}
m_interpreter = &interpreter;
s_owner = &interpreter;
s_depth = 1;
}
PythonRuntimeLease::PythonRuntimeLease(PythonRuntimeLease&& other) noexcept
: m_interpreter(other.m_interpreter), m_lock(std::move(other.m_lock))
{
other.m_interpreter = nullptr;
}
PythonRuntimeLease& PythonRuntimeLease::operator=(PythonRuntimeLease&& other) noexcept
{
if (this != &other) {
release();
m_lock = std::move(other.m_lock);
m_interpreter = other.m_interpreter;
other.m_interpreter = nullptr;
}
return *this;
}
void PythonRuntimeLease::release()
{
if (!m_interpreter)
return;
if (s_owner == m_interpreter && s_depth > 0) {
--s_depth;
if (s_depth == 0)
s_owner = nullptr;
}
m_interpreter = nullptr;
}
PythonRuntimeLease::~PythonRuntimeLease()
{
release();
}
void log_python_exception_keep(pybind11::error_already_set& err)
{
// The GIL may already be released here: the macro's gil_scoped_acquire is a
// local destroyed by stack unwinding before this catch runs. Touching Python
// state below needs the GIL. PyGILState_Ensure is reentrant — harmless if held.
PythonGILState gil;
if (!gil)
return;
// Non-destructive: print the traceback to sys.stderr (tee'd to the session log)
// WITHOUT consuming err, so the caller can rethrow it intact. For example, downstream C++
@@ -108,8 +168,11 @@ std::string executable_name(const char* base)
#endif
}
bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error)
bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error, bool* inserted = nullptr)
{
if (inserted)
*inserted = false;
PyObject* sys_path = PySys_GetObject("path");
if (!sys_path || !PyList_Check(sys_path)) {
error = "Python sys.path is not available";
@@ -136,6 +199,9 @@ bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error)
return false;
}
if (inserted)
*inserted = true;
return true;
}
@@ -387,94 +453,23 @@ std::string PythonInterpreter::bundled_uv_path()
bool PythonInterpreter::initialize()
{
if (m_initialized) {
std::unique_lock<std::shared_mutex> runtime_lock(m_runtime_mutex);
if (m_initialized.load(std::memory_order_acquire)) {
return true;
}
m_last_error.clear();
m_plugin_path_users.clear();
m_plugin_path_owned.clear();
m_plugin_module_users.clear();
m_plugin_module_owned.clear();
try {
// Set Python home to the bundled Python installation
// This is critical for finding the standard library (encodings module, etc.)
namespace fs = boost::filesystem;
std::string python_home;
const auto valid_python_home = [](const fs::path& candidate) {
#ifdef _WIN32
return fs::exists(candidate / "Lib" / "encodings") &&
(fs::exists(candidate / PYTHON_DLL) || fs::exists(candidate / PYTHON_DEBUG_DLL));
#else
return fs::exists(candidate / "lib" / PYTHON_STDLIB_DIR / "encodings");
#endif
};
// Determine Python home based on application structure
// Python is bundled at different locations depending on platform and build type
// Strategy 1: Platform-specific bundled locations (highest priority)
#ifdef __APPLE__
// macOS app bundle: OrcaSlicer.app/Contents/MacOS/python
// (resources_dir is Contents/Resources, so go up and into MacOS)
fs::path bundle_python = fs::path(resources_dir()).parent_path() / "MacOS" / "python";
if (valid_python_home(bundle_python)) {
python_home = bundle_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in macOS app bundle: " << python_home;
}
#elif defined(_WIN32)
fs::path exe_python = boost::dll::program_location().parent_path() / "python";
if (valid_python_home(exe_python)) {
python_home = exe_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python next to Windows executable: " << python_home;
}
#else
// Linux: typically in ../lib or ../share relative to binary
fs::path linux_python = fs::path(resources_dir()).parent_path() / "lib" / "python";
if (valid_python_home(linux_python)) {
python_home = linux_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in Linux install: " << python_home;
}
#endif
// Strategy 2: Configured development dependency directory.
if (python_home.empty()) {
fs::path configured_python = ORCA_BUNDLED_PYTHON_ROOT;
if (!configured_python.empty() && valid_python_home(configured_python)) {
python_home = configured_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in configured bundled path: " << python_home;
}
}
// Strategy 3: Development build directory from runtime environment.
if (python_home.empty()) {
const char* prefix_path = std::getenv("CMAKE_PREFIX_PATH");
if (prefix_path && std::strlen(prefix_path) > 0) {
fs::path libpython = fs::path(prefix_path) / "libpython";
if (valid_python_home(libpython)) {
python_home = libpython.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in CMAKE_PREFIX_PATH: " << python_home;
}
}
}
// Strategy 3: Check resources directory (alternate bundling location)
if (python_home.empty()) {
fs::path res_python = fs::path(resources_dir()) / "python";
if (valid_python_home(res_python)) {
python_home = res_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in resources directory: " << python_home;
}
}
// Strategy 4: Check data_dir (user configuration directory)
#ifndef _WIN32
if (python_home.empty()) {
fs::path data_python = fs::path(data_dir()) / "python";
if (valid_python_home(data_python)) {
python_home = data_python.string();
BOOST_LOG_TRIVIAL(info) << "Found Python in data directory: " << python_home;
}
}
#endif
const std::string python_home = find_bundled_python_home().string();
if (python_home.empty()) {
m_last_error = "Could not locate bundled Python installation";
@@ -493,6 +488,8 @@ bool PythonInterpreter::initialize()
return false;
}
BOOST_LOG_TRIVIAL(info) << "Found bundled Python home: " << python_home;
// Verify Python standard library directory exists
#ifdef _WIN32
fs::path python_lib = fs::path(python_home) / "Lib";
@@ -539,6 +536,10 @@ bool PythonInterpreter::initialize()
// Set Python home - this is the prefix where Python libraries are located
PyConfig config;
PyConfig_InitPythonConfig(&config);
// Do not let the host process's PYTHONPATH or user site-packages override the bundled
// runtime used by plugins.
config.use_environment = 0;
config.user_site_directory = 0;
BOOST_LOG_TRIVIAL(debug) << "Calling PyConfig_SetBytesString with home=" << python_home;
PyStatus status = PyConfig_SetBytesString(&config, &config.home, python_home.c_str());
@@ -631,11 +632,10 @@ bool PythonInterpreter::initialize()
// background-thread exceptions) to <data_dir>/log/python_*.log.
install_python_stderr_redirect();
m_initialized = true;
// Release the GIL so other threads can acquire it via PyGILState_Ensure.
// Without this, calls from background threads will block trying to acquire the GIL.
m_main_thread_state = PyEval_SaveThread();
m_initialized.store(true, std::memory_order_release);
BOOST_LOG_TRIVIAL(debug) << "Main thread released Python GIL after initialization";
return true;
@@ -650,7 +650,8 @@ PythonInterpreter::~PythonInterpreter() { shutdown(); }
void PythonInterpreter::shutdown()
{
if (!m_initialized)
std::unique_lock<std::shared_mutex> runtime_lock(m_runtime_mutex);
if (!m_initialized.load(std::memory_order_acquire))
return;
BOOST_LOG_TRIVIAL(info) << "Python interpreter shutdown enter";
@@ -668,80 +669,305 @@ void PythonInterpreter::shutdown()
m_interpreter.reset();
BOOST_LOG_TRIVIAL(info) << "Python interpreter finalized";
m_initialized = false;
m_plugin_path_users.clear();
m_plugin_path_owned.clear();
m_plugin_module_users.clear();
m_plugin_module_owned.clear();
m_initialized.store(false, std::memory_order_release);
}
bool PythonInterpreter::add_sys_path(const std::string& path, std::string& error)
bool PythonInterpreter::add_plugin_sys_path(const std::string& path, std::string& error)
{
if (!m_initialized) {
if (!m_initialized.load(std::memory_order_acquire)) {
error = "Python interpreter not initialized";
return false;
}
PythonGILState gil;
if (!gil) {
error = "Python interpreter is shutting down";
return false;
}
return add_plugin_sys_path_locked(path, error);
}
bool PythonInterpreter::add_plugin_sys_path_locked(const std::string& path, std::string& error)
{
bool inserted = false;
if (!add_sys_path_entry(boost::filesystem::path(path), error, &inserted))
return false;
auto users = m_plugin_path_users.find(path);
if (users == m_plugin_path_users.end()) {
m_plugin_path_owned[path] = inserted;
m_plugin_path_users.emplace(path, 1);
} else {
++users->second;
}
return true;
}
void PythonInterpreter::remove_plugin_sys_paths_locked(const std::vector<std::string>& paths)
{
PyObject* sys_path = PySys_GetObject("path");
if (!sys_path || !PyList_Check(sys_path)) {
error = "Python sys.path is not available";
return false;
PyErr_Clear();
return;
}
PyObjectPtr py_path(PyUnicode_DecodeFSDefault(path.c_str()));
if (!py_path) {
error = "Failed to decode path for Python sys.path: " + path;
PyErr_Clear();
return false;
for (const std::string& path : paths) {
auto users = m_plugin_path_users.find(path);
if (users == m_plugin_path_users.end())
continue;
if (--users->second != 0)
continue;
const bool owned = m_plugin_path_owned[path];
m_plugin_path_users.erase(users);
m_plugin_path_owned.erase(path);
if (!owned)
continue;
PyObjectPtr py_path(PyUnicode_DecodeFSDefault(path.c_str()));
if (!py_path) {
PyErr_Clear();
continue;
}
for (Py_ssize_t index = PyList_Size(sys_path) - 1; index >= 0; --index) {
PyObject* entry = PyList_GetItem(sys_path, index); // borrowed reference
int equal = entry ? PyObject_RichCompareBool(entry, py_path.get(), Py_EQ) : 0;
if (equal == 1 && PySequence_DelItem(sys_path, index) != 0)
PyErr_Clear();
else if (equal < 0)
PyErr_Clear();
}
}
const int contains = PySequence_Contains(sys_path, py_path.get());
if (contains == 1)
return true;
if (contains < 0)
PyErr_Clear();
if (PyList_Insert(sys_path, 0, py_path.get()) != 0) {
error = "Failed to append path to Python sys.path: " + path;
PyErr_Clear();
return false;
}
return true;
}
bool PythonInterpreter::execute_string(const std::string& code, std::string& error)
void PythonInterpreter::record_plugin_modules_locked(const std::string& module_name,
const std::vector<std::string>& plugin_paths,
std::vector<std::string>* plugin_modules)
{
if (!m_initialized) {
error = "Python interpreter not initialized";
return false;
if (!plugin_modules)
return;
PyObject* modules = PyImport_GetModuleDict();
if (!modules)
return;
PyObjectPtr names(PyDict_Keys(modules));
if (!names) {
PyErr_Clear();
return;
}
const std::string prefix = module_name + ".";
const auto path_matches = [](const std::string& file, const std::string& root) {
namespace fs = boost::filesystem;
boost::system::error_code ec;
fs::path file_path = fs::weakly_canonical(fs::path(file), ec);
if (ec) {
ec.clear();
file_path = fs::absolute(fs::path(file), ec);
}
if (ec)
return false;
ec.clear();
fs::path root_path = fs::weakly_canonical(fs::path(root), ec);
if (ec) {
ec.clear();
root_path = fs::absolute(fs::path(root), ec);
}
if (ec)
return false;
const std::string file_string = file_path.generic_string();
std::string root_string = root_path.generic_string();
if (root_string.empty())
return false;
if (root_string.back() != '/')
root_string.push_back('/');
return file_string.compare(0, root_string.size(), root_string) == 0;
};
const Py_ssize_t count = PyList_Size(names.get());
for (Py_ssize_t index = 0; index < count; ++index) {
PyObject* name_obj = PyList_GetItem(names.get(), index); // borrowed reference
if (!name_obj || !PyUnicode_Check(name_obj))
continue;
const char* name_utf8 = PyUnicode_AsUTF8(name_obj);
if (!name_utf8) {
PyErr_Clear();
continue;
}
const std::string name(name_utf8);
const bool is_plugin_namespace = name == module_name || name.compare(0, prefix.size(), prefix) == 0;
bool is_plugin_path_module = false;
if (!is_plugin_namespace) {
PyObject* module = PyDict_GetItem(modules, name_obj); // borrowed reference
if (!module)
continue;
PyObjectPtr file(PyObject_GetAttrString(module, "__file__"));
if (file && PyUnicode_Check(file.get())) {
const char* file_utf8 = PyUnicode_AsUTF8(file.get());
if (file_utf8) {
for (const std::string& path : plugin_paths) {
if (path_matches(file_utf8, path)) {
is_plugin_path_module = true;
break;
}
}
} else {
PyErr_Clear();
}
} else {
PyErr_Clear();
}
if (!is_plugin_path_module) {
PyObjectPtr package_path(PyObject_GetAttrString(module, "__path__"));
if (package_path) {
const Py_ssize_t path_count = PySequence_Size(package_path.get());
if (path_count < 0) {
PyErr_Clear();
} else {
for (Py_ssize_t path_index = 0; path_index < path_count && !is_plugin_path_module; ++path_index) {
PyObjectPtr path_entry(PySequence_GetItem(package_path.get(), path_index));
if (!path_entry) {
PyErr_Clear();
continue;
}
if (!PyUnicode_Check(path_entry.get()))
continue;
const char* path_utf8 = PyUnicode_AsUTF8(path_entry.get());
if (!path_utf8) {
PyErr_Clear();
continue;
}
for (const std::string& path : plugin_paths) {
if (path_matches(path_utf8, path)) {
is_plugin_path_module = true;
break;
}
}
}
}
} else {
PyErr_Clear();
}
}
}
if (!is_plugin_namespace && !is_plugin_path_module)
continue;
auto users = m_plugin_module_users.find(name);
if (users == m_plugin_module_users.end()) {
m_plugin_module_owned[name] = true;
m_plugin_module_users.emplace(name, 1);
} else {
++users->second;
}
plugin_modules->push_back(name);
}
}
void PythonInterpreter::remove_plugin_modules_locked(const std::vector<std::string>& plugin_modules)
{
PyObject* modules = PyImport_GetModuleDict();
if (!modules)
return;
for (const std::string& name : plugin_modules) {
auto users = m_plugin_module_users.find(name);
if (users == m_plugin_module_users.end())
continue;
if (--users->second != 0)
continue;
const bool owned = m_plugin_module_owned[name];
m_plugin_module_users.erase(users);
m_plugin_module_owned.erase(name);
if (owned && PyDict_DelItemString(modules, name.c_str()) != 0)
PyErr_Clear();
}
}
void PythonInterpreter::remove_module_tree_locked(const std::string& module_name)
{
if (module_name.empty())
return;
PyObject* modules = PyImport_GetModuleDict();
if (!modules)
return;
PyObjectPtr names(PyDict_Keys(modules));
if (!names) {
PyErr_Clear();
return;
}
const std::string prefix = module_name + ".";
const Py_ssize_t count = PyList_Size(names.get());
for (Py_ssize_t index = 0; index < count; ++index) {
PyObject* name_obj = PyList_GetItem(names.get(), index); // borrowed reference
if (!name_obj || !PyUnicode_Check(name_obj))
continue;
const char* name_utf8 = PyUnicode_AsUTF8(name_obj);
if (!name_utf8) {
PyErr_Clear();
continue;
}
const std::string name(name_utf8);
if (name != module_name && name.compare(0, prefix.size(), prefix) != 0)
continue;
if (PyDict_DelItem(modules, name_obj) != 0)
PyErr_Clear();
}
}
void PythonInterpreter::unload_module(PyObject* module,
const std::string& module_name,
const std::vector<std::string>& plugin_paths,
const std::vector<std::string>& plugin_modules)
{
if (!module && plugin_paths.empty() && plugin_modules.empty())
return;
if (!m_initialized.load(std::memory_order_acquire))
return;
PythonGILState gil;
if (!gil)
return;
PyObject* main_module = PyImport_AddModule("__main__");
if (!main_module) {
error = "Failed to get __main__ module";
return false;
}
PyObject* global_dict = PyModule_GetDict(main_module);
PyObjectPtr result(PyRun_String(code.c_str(), Py_file_input, global_dict, global_dict));
if (!result) {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
error = format_python_error(ptype, pvalue, ptraceback);
Py_XDECREF(ptype);
Py_XDECREF(pvalue);
Py_XDECREF(ptraceback);
return false;
}
return true;
remove_plugin_modules_locked(plugin_modules);
if (plugin_modules.empty())
remove_module_tree_locked(module_name);
remove_plugin_sys_paths_locked(plugin_paths);
Py_XDECREF(module);
}
PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path, std::string& error)
PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
std::string& error,
std::vector<std::string>* plugin_paths,
std::vector<std::string>* plugin_modules)
{
if (!m_initialized) {
if (!m_initialized.load(std::memory_order_acquire)) {
error = "Python interpreter not initialized";
return nullptr;
}
@@ -755,49 +981,27 @@ PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
}
PythonGILState gil;
if (!gil) {
error = "Python interpreter is shutting down";
return nullptr;
}
// Add the directory to sys.path
fs::path dir_path = path.parent_path();
std::string module_name = path.stem().string();
PyObjectPtr sys(PyImport_ImportModule("sys"));
if (!sys) {
error = "Failed to import sys module";
const std::string dir = dir_path.string();
if (!add_plugin_sys_path_locked(dir, error))
return nullptr;
}
PyObject* sys_path = PyObject_GetAttrString(sys.get(), "path");
if (!sys_path) {
error = "Failed to get sys.path";
return nullptr;
}
PyObjectPtr dir_str(PyUnicode_FromString(dir_path.string().c_str()));
if (!dir_str) {
Py_DECREF(sys_path);
error = "Failed to create directory string";
return nullptr;
}
if (PyList_Insert(sys_path, 0, dir_str.get()) < 0) {
Py_DECREF(sys_path);
error = "Failed to add directory to sys.path";
return nullptr;
}
Py_DECREF(sys_path);
if (plugin_paths)
plugin_paths->push_back(dir);
// Ensure module is re-imported fresh by removing any cached instance.
if (PyObject* modules = PyImport_GetModuleDict()) {
if (PyDict_GetItemString(modules, module_name.c_str())) {
if (PyDict_DelItemString(modules, module_name.c_str()) != 0) {
PyErr_Clear();
}
}
}
remove_module_tree_locked(module_name);
// Import the module
PyObject* module = PyImport_ImportModule(module_name.c_str());
record_plugin_modules_locked(module_name, plugin_paths ? *plugin_paths : std::vector<std::string>{}, plugin_modules);
if (!module) {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
@@ -811,9 +1015,13 @@ PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
return module;
}
PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_path, const std::string& pkg_name, std::string& error)
PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_path,
const std::string& pkg_name,
std::string& error,
std::vector<std::string>* plugin_paths,
std::vector<std::string>* plugin_modules)
{
if (!m_initialized) {
if (!m_initialized.load(std::memory_order_acquire)) {
error = "Python interpreter not initialized";
return nullptr;
}
@@ -827,43 +1035,21 @@ PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_p
}
PythonGILState gil;
PyObjectPtr sys(PyImport_ImportModule("sys"));
if (!sys) {
error = "Failed to import sys module";
if (!gil) {
error = "Python interpreter is shutting down";
return nullptr;
}
PyObject* sys_path = PyObject_GetAttrString(sys.get(), "path");
if (!sys_path) {
error = "Failed to get sys.path";
const std::string directory = dir.string();
if (!add_plugin_sys_path_locked(directory, error))
return nullptr;
}
if (plugin_paths)
plugin_paths->push_back(directory);
PyObjectPtr dir_str(PyUnicode_FromString(dir.string().c_str()));
if (!dir_str) {
Py_DECREF(sys_path);
error = "Failed to create directory string";
return nullptr;
}
if (PyList_Insert(sys_path, 0, dir_str.get()) < 0) {
Py_DECREF(sys_path);
error = "Failed to add directory to sys.path";
return nullptr;
}
Py_DECREF(sys_path);
if (PyObject* modules = PyImport_GetModuleDict()) {
if (PyDict_GetItemString(modules, pkg_name.c_str())) {
if (PyDict_DelItemString(modules, pkg_name.c_str()) != 0) {
PyErr_Clear();
}
}
}
remove_module_tree_locked(pkg_name);
PyObject* module = PyImport_ImportModule(pkg_name.c_str());
record_plugin_modules_locked(pkg_name, plugin_paths ? *plugin_paths : std::vector<std::string>{}, plugin_modules);
if (!module) {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
@@ -877,9 +1063,13 @@ PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_p
return module;
}
PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path, const std::string& pkg_name, std::string& error)
PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path,
const std::string& pkg_name,
std::string& error,
std::vector<std::string>* plugin_paths,
std::vector<std::string>* plugin_modules)
{
if (!m_initialized) {
if (!m_initialized.load(std::memory_order_acquire)) {
error = "Python interpreter not initialized";
return nullptr;
}
@@ -900,98 +1090,7 @@ PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path,
return nullptr;
}
return load_module_from_directory(extract_dir.string(), pkg_name, error);
}
bool PythonInterpreter::call_function(
PyObject* module, const std::string& function_name, const std::string& arg, std::string& result, std::string& error)
{
if (!m_initialized || !module) {
error = "Python interpreter not initialized or module is null";
return false;
}
PythonGILState gil;
PyObject* func = PyObject_GetAttrString(module, function_name.c_str());
if (!func || !PyCallable_Check(func)) {
Py_XDECREF(func);
error = "Function '" + function_name + "' not found or not callable";
return false;
}
PyObjectPtr args(PyTuple_New(1));
PyObjectPtr arg_str(PyUnicode_FromString(arg.c_str()));
PyTuple_SetItem(args.get(), 0, arg_str.release());
PyObjectPtr py_result(PyObject_CallObject(func, args.get()));
Py_DECREF(func);
if (!py_result) {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
error = "Function call failed: " + format_python_error(ptype, pvalue, ptraceback);
Py_XDECREF(ptype);
Py_XDECREF(pvalue);
Py_XDECREF(ptraceback);
return false;
}
result = py_object_to_string(py_result.get());
return true;
}
bool PythonInterpreter::call_function_no_args(PyObject* module, const std::string& function_name, std::string& result, std::string& error)
{
if (!m_initialized || !module) {
error = "Python interpreter not initialized or module is null";
return false;
}
PythonGILState gil;
PyObject* func = PyObject_GetAttrString(module, function_name.c_str());
if (!func || !PyCallable_Check(func)) {
Py_XDECREF(func);
error = "Function '" + function_name + "' not found or not callable";
return false;
}
PyObjectPtr py_result(PyObject_CallObject(func, nullptr));
Py_DECREF(func);
if (!py_result) {
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
error = "Function call failed: " + format_python_error(ptype, pvalue, ptraceback);
Py_XDECREF(ptype);
Py_XDECREF(pvalue);
Py_XDECREF(ptraceback);
return false;
}
result = py_object_to_string(py_result.get());
return true;
}
std::string PythonInterpreter::py_object_to_string(PyObject* obj)
{
if (!obj) {
return "";
}
if (PyUnicode_Check(obj)) {
const char* str = PyUnicode_AsUTF8(obj);
return str ? std::string(str) : "";
}
PyObjectPtr str_obj(PyObject_Str(obj));
if (str_obj) {
const char* str = PyUnicode_AsUTF8(str_obj.get());
return str ? std::string(str) : "";
}
return "";
return load_module_from_directory(extract_dir.string(), pkg_name, error, plugin_paths, plugin_modules);
}
} // namespace Slic3r