Some checks failed
Build and Test / build (push) Failing after 2m13s
- Add openDocumentInteractive() and saveDocumentAsInteractive() to FileOriginPython - Fix Console API: Warning -> warning, Error -> error in OriginManager.cpp - These methods bridge Python origins to the new interactive document operations
625 lines
18 KiB
C++
625 lines
18 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2025 Kindred Systems *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Document.h>
|
|
#include <App/DocumentPy.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/PyObjectBase.h>
|
|
|
|
#include "FileOriginPython.h"
|
|
#include "OriginManager.h"
|
|
#include "BitmapFactory.h"
|
|
|
|
|
|
namespace Gui {
|
|
|
|
std::vector<FileOriginPython*> FileOriginPython::_instances;
|
|
|
|
void FileOriginPython::addOrigin(const Py::Object& obj)
|
|
{
|
|
// Check if already registered
|
|
if (findOrigin(obj)) {
|
|
Base::Console().warning("FileOriginPython: Origin already registered\n");
|
|
return;
|
|
}
|
|
|
|
auto* origin = new FileOriginPython(obj);
|
|
|
|
// Cache the ID immediately for registration
|
|
origin->_cachedId = origin->callStringMethod("id");
|
|
if (origin->_cachedId.empty()) {
|
|
Base::Console().error("FileOriginPython: Origin must have non-empty id()\n");
|
|
delete origin;
|
|
return;
|
|
}
|
|
|
|
_instances.push_back(origin);
|
|
|
|
// Register with OriginManager
|
|
if (!OriginManager::instance()->registerOrigin(origin)) {
|
|
// Registration failed - remove from our instances list
|
|
// (registerOrigin already deleted the origin)
|
|
_instances.pop_back();
|
|
}
|
|
}
|
|
|
|
void FileOriginPython::removeOrigin(const Py::Object& obj)
|
|
{
|
|
FileOriginPython* origin = findOrigin(obj);
|
|
if (!origin) {
|
|
return;
|
|
}
|
|
|
|
std::string originId = origin->_cachedId;
|
|
|
|
// Remove from instances list
|
|
auto it = std::find(_instances.begin(), _instances.end(), origin);
|
|
if (it != _instances.end()) {
|
|
_instances.erase(it);
|
|
}
|
|
|
|
// Unregister from OriginManager (this will delete the origin)
|
|
OriginManager::instance()->unregisterOrigin(originId);
|
|
}
|
|
|
|
FileOriginPython* FileOriginPython::findOrigin(const Py::Object& obj)
|
|
{
|
|
for (auto* instance : _instances) {
|
|
if (instance->_inst == obj) {
|
|
return instance;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FileOriginPython::FileOriginPython(const Py::Object& obj)
|
|
: _inst(obj)
|
|
{
|
|
}
|
|
|
|
FileOriginPython::~FileOriginPython() = default;
|
|
|
|
Py::Object FileOriginPython::callMethod(const char* method) const
|
|
{
|
|
return callMethod(method, Py::Tuple());
|
|
}
|
|
|
|
Py::Object FileOriginPython::callMethod(const char* method, const Py::Tuple& args) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr(method)) {
|
|
Py::Callable func(_inst.getAttr(method));
|
|
return func.apply(args);
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return Py::None();
|
|
}
|
|
|
|
bool FileOriginPython::callBoolMethod(const char* method, bool defaultValue) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr(method)) {
|
|
Py::Callable func(_inst.getAttr(method));
|
|
Py::Object result = func.apply(Py::Tuple());
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
std::string FileOriginPython::callStringMethod(const char* method, const std::string& defaultValue) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr(method)) {
|
|
Py::Callable func(_inst.getAttr(method));
|
|
Py::Object result = func.apply(Py::Tuple());
|
|
if (result.isString()) {
|
|
return Py::String(result).as_std_string();
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
Py::Object FileOriginPython::getDocPyObject(App::Document* doc) const
|
|
{
|
|
if (!doc) {
|
|
return Py::None();
|
|
}
|
|
return Py::asObject(doc->getPyObject());
|
|
}
|
|
|
|
// Identity methods
|
|
std::string FileOriginPython::id() const
|
|
{
|
|
return _cachedId.empty() ? callStringMethod("id") : _cachedId;
|
|
}
|
|
|
|
std::string FileOriginPython::name() const
|
|
{
|
|
return callStringMethod("name", id());
|
|
}
|
|
|
|
std::string FileOriginPython::nickname() const
|
|
{
|
|
return callStringMethod("nickname", name());
|
|
}
|
|
|
|
QIcon FileOriginPython::icon() const
|
|
{
|
|
std::string iconName = callStringMethod("icon", "document-new");
|
|
return BitmapFactory().iconFromTheme(iconName.c_str());
|
|
}
|
|
|
|
OriginType FileOriginPython::type() const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("type")) {
|
|
Py::Callable func(_inst.getAttr("type"));
|
|
Py::Object result = func.apply(Py::Tuple());
|
|
if (result.isNumeric()) {
|
|
int t = static_cast<int>(Py::Long(result));
|
|
if (t >= 0 && t <= static_cast<int>(OriginType::Custom)) {
|
|
return static_cast<OriginType>(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return OriginType::Custom;
|
|
}
|
|
|
|
// Workflow characteristics
|
|
bool FileOriginPython::tracksExternally() const
|
|
{
|
|
return callBoolMethod("tracksExternally", false);
|
|
}
|
|
|
|
bool FileOriginPython::requiresAuthentication() const
|
|
{
|
|
return callBoolMethod("requiresAuthentication", false);
|
|
}
|
|
|
|
// Capability queries
|
|
bool FileOriginPython::supportsRevisions() const
|
|
{
|
|
return callBoolMethod("supportsRevisions", false);
|
|
}
|
|
|
|
bool FileOriginPython::supportsBOM() const
|
|
{
|
|
return callBoolMethod("supportsBOM", false);
|
|
}
|
|
|
|
bool FileOriginPython::supportsPartNumbers() const
|
|
{
|
|
return callBoolMethod("supportsPartNumbers", false);
|
|
}
|
|
|
|
bool FileOriginPython::supportsAssemblies() const
|
|
{
|
|
return callBoolMethod("supportsAssemblies", false);
|
|
}
|
|
|
|
// Connection state
|
|
ConnectionState FileOriginPython::connectionState() const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("connectionState")) {
|
|
Py::Callable func(_inst.getAttr("connectionState"));
|
|
Py::Object result = func.apply(Py::Tuple());
|
|
if (result.isNumeric()) {
|
|
int s = static_cast<int>(Py::Long(result));
|
|
if (s >= 0 && s <= static_cast<int>(ConnectionState::Error)) {
|
|
return static_cast<ConnectionState>(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return ConnectionState::Connected;
|
|
}
|
|
|
|
bool FileOriginPython::connect()
|
|
{
|
|
return callBoolMethod("connect", true);
|
|
}
|
|
|
|
void FileOriginPython::disconnect()
|
|
{
|
|
callMethod("disconnect");
|
|
}
|
|
|
|
// Document identity
|
|
std::string FileOriginPython::documentIdentity(App::Document* doc) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("documentIdentity")) {
|
|
Py::Callable func(_inst.getAttr("documentIdentity"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isString()) {
|
|
return Py::String(result).as_std_string();
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::string FileOriginPython::documentDisplayId(App::Document* doc) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("documentDisplayId")) {
|
|
Py::Callable func(_inst.getAttr("documentDisplayId"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isString()) {
|
|
return Py::String(result).as_std_string();
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return documentIdentity(doc);
|
|
}
|
|
|
|
bool FileOriginPython::ownsDocument(App::Document* doc) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("ownsDocument")) {
|
|
Py::Callable func(_inst.getAttr("ownsDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileOriginPython::syncProperties(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("syncProperties")) {
|
|
Py::Callable func(_inst.getAttr("syncProperties"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Core document operations
|
|
App::Document* FileOriginPython::newDocument(const std::string& name)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("newDocument")) {
|
|
Py::Callable func(_inst.getAttr("newDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, Py::String(name));
|
|
Py::Object result = func.apply(args);
|
|
if (!result.isNone()) {
|
|
// Extract App::Document* from Python object
|
|
if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) {
|
|
return static_cast<App::DocumentPy*>(result.ptr())->getDocumentPtr();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
App::Document* FileOriginPython::openDocument(const std::string& identity)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("openDocument")) {
|
|
Py::Callable func(_inst.getAttr("openDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, Py::String(identity));
|
|
Py::Object result = func.apply(args);
|
|
if (!result.isNone()) {
|
|
if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) {
|
|
return static_cast<App::DocumentPy*>(result.ptr())->getDocumentPtr();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool FileOriginPython::saveDocument(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("saveDocument")) {
|
|
Py::Callable func(_inst.getAttr("saveDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileOriginPython::saveDocumentAs(App::Document* doc, const std::string& newIdentity)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("saveDocumentAs")) {
|
|
Py::Callable func(_inst.getAttr("saveDocumentAs"));
|
|
Py::Tuple args(2);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
args.setItem(1, Py::String(newIdentity));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Extended operations
|
|
bool FileOriginPython::commitDocument(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("commitDocument")) {
|
|
Py::Callable func(_inst.getAttr("commitDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileOriginPython::pullDocument(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("pullDocument")) {
|
|
Py::Callable func(_inst.getAttr("pullDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileOriginPython::pushDocument(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("pushDocument")) {
|
|
Py::Callable func(_inst.getAttr("pushDocument"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FileOriginPython::showInfo(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("showInfo")) {
|
|
Py::Callable func(_inst.getAttr("showInfo"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
func.apply(args);
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
}
|
|
|
|
void FileOriginPython::showBOM(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("showBOM")) {
|
|
Py::Callable func(_inst.getAttr("showBOM"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
func.apply(args);
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
}
|
|
|
|
App::Document* FileOriginPython::openDocumentInteractive()
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("openDocumentInteractive")) {
|
|
Py::Callable func(_inst.getAttr("openDocumentInteractive"));
|
|
Py::Object result = func.apply(Py::Tuple());
|
|
if (!result.isNone()) {
|
|
if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) {
|
|
return static_cast<App::DocumentPy*>(result.ptr())->getDocumentPtr();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool FileOriginPython::saveDocumentAsInteractive(App::Document* doc)
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
try {
|
|
if (_inst.hasAttr("saveDocumentAsInteractive")) {
|
|
Py::Callable func(_inst.getAttr("saveDocumentAsInteractive"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, getDocPyObject(doc));
|
|
Py::Object result = func.apply(args);
|
|
if (result.isBoolean()) {
|
|
return Py::Boolean(result);
|
|
}
|
|
if (result.isNumeric()) {
|
|
return Py::Long(result) != 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace Gui
|