Files
create/src/Gui/ApplicationPy.cpp
forbes-0023 7535a48ec4 feat(gui): add origin abstraction layer for unified file operations
Implements Issue #9: Origin abstraction layer

This commit introduces a foundational abstraction for document origins,
enabling FreeCAD to work with different storage backends (local filesystem,
Silo PLM, future cloud services) through a unified interface.

## Core Components

### FileOrigin Abstract Base Class (FileOrigin.h/cpp)
- Defines interface for document origin handlers
- Identity methods: id(), name(), nickname(), icon(), type()
- Workflow characteristics: tracksExternally(), requiresAuthentication()
- Capability queries: supportsRevisions(), supportsBOM(), supportsPartNumbers()
- Connection state management with fastsignals notifications
- Document identity: documentIdentity() returns UUID, documentDisplayId() for display
- Property sync: syncProperties() for bidirectional database sync
- Core operations: newDocument(), openDocument(), saveDocument(), saveDocumentAs()
- Extended PLM operations: commitDocument(), pullDocument(), pushDocument(), etc.

### LocalFileOrigin Implementation
- Default origin for local filesystem documents
- ownsDocument(): Returns true if document has NO SiloItemId property
- Wraps existing FreeCAD file operations (App::GetApplication())

### OriginManager Singleton (OriginManager.h/cpp)
- Follows WorkbenchManager pattern (instance()/destruct())
- Manages registered FileOrigin instances
- Tracks current origin selection with persistence
- Provides document-to-origin resolution via findOwningOrigin()
- Emits signals: signalOriginRegistered, signalOriginUnregistered,
  signalCurrentOriginChanged
- Preferences stored at: User parameter:BaseApp/Preferences/General/Origin

### Python Bindings (FileOriginPython.h/cpp)
- Adapts Python objects to FileOrigin C++ interface
- Enables Silo addon to implement origins in Python
- Thread-safe with Base::PyGILStateLocker
- Static addOrigin()/removeOrigin() for registration

### Python API (ApplicationPy.cpp)
- FreeCADGui.addOrigin(obj) - Register Python origin
- FreeCADGui.removeOrigin(obj) - Unregister Python origin
- FreeCADGui.getOrigin(id) - Get origin info as dict
- FreeCADGui.listOrigins() - List all registered origin IDs
- FreeCADGui.activeOrigin() - Get current origin info
- FreeCADGui.setActiveOrigin(id) - Set active origin

## Design Decisions

1. **UUID Tracking**: Documents tracked by SiloItemId (immutable UUID),
   SiloPartNumber used for human-readable display only

2. **Ownership by Properties**: Origin ownership determined by document
   properties (SiloItemId), not file path location

3. **Local Storage Always**: All documents saved locally; origins change
   workflow and identity model, not storage location

4. **Property Syncing**: syncProperties() enables bidirectional sync of
   document metadata with database (Description, SourcingType, etc.)

## Files Added
- src/Gui/FileOrigin.h
- src/Gui/FileOrigin.cpp
- src/Gui/FileOriginPython.h
- src/Gui/FileOriginPython.cpp
- src/Gui/OriginManager.h
- src/Gui/OriginManager.cpp

## Files Modified
- src/Gui/CMakeLists.txt - Added new source files
- src/Gui/Application.cpp - Initialize/destruct OriginManager
- src/Gui/ApplicationPy.h - Added Python method declarations
- src/Gui/ApplicationPy.cpp - Added Python method implementations

Refs: #9
2026-02-05 13:17:23 -06:00

2142 lines
65 KiB
C++

/***************************************************************************
* Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <QApplication>
#include <QDir>
#include <QPrinter>
#include <QFileInfo>
#include <Inventor/SoInput.h>
#include <Inventor/actions/SoGetPrimitiveCountAction.h>
#include <Inventor/nodes/SoSeparator.h>
#include <xercesc/util/TranscodingException.hpp>
#include <xercesc/util/XMLString.hpp>
#include <boost/regex.hpp>
#include <App/DocumentObjectPy.h>
#include <App/DocumentPy.h>
#include <App/PropertyFile.h>
#include <Base/Interpreter.h>
#include <Base/Console.h>
#include <Base/PyWrapParseTupleAndKeywords.h>
#include <CXX/Objects.hxx>
#include <Gui/PreferencePages/DlgSettingsPDF.h>
#include "Application.h"
#include "ApplicationPy.h"
#include "BitmapFactory.h"
#include "Command.h"
#include "Dialogs/DlgPreferencesImp.h"
#include "Document.h"
#include "DocumentObserverPython.h"
#include "DownloadManager.h"
#include "EditorView.h"
#include "FileHandler.h"
#include "Macro.h"
#include "MainWindow.h"
#include "MainWindowPy.h"
#include "PythonEditor.h"
#include "PythonWrapper.h"
#include "SoFCDB.h"
#include "SplitView3DInventor.h"
#include "View3DInventor.h"
#include "ViewProvider.h"
#include "WaitCursor.h"
#include "WidgetFactory.h"
#include "Workbench.h"
#include "WorkbenchManager.h"
#include "WorkbenchManipulatorPython.h"
#include "FileOriginPython.h"
#include "OriginManager.h"
#include "Inventor/MarkerBitmaps.h"
#include "Language/Translator.h"
using namespace Gui;
// Application methods structure
PyMethodDef ApplicationPy::Methods[] = {
{"activateWorkbench",
(PyCFunction)ApplicationPy::sActivateWorkbenchHandler,
METH_VARARGS,
"activateWorkbench(name) -> bool\n"
"\n"
"Activate workbench by its name. Return False if the workbench is\n"
"already active.\n"
"\n"
"name : str\n Name of the workbench to activate."},
{"addWorkbench",
(PyCFunction)ApplicationPy::sAddWorkbenchHandler,
METH_VARARGS,
"addWorkbench(workbench) -> None\n"
"\n"
"Add a workbench.\n"
"\n"
"workbench : Workbench, Workbench type\n"
" Instance of a Workbench subclass or subclass of the\n"
" Workbench class."},
{"removeWorkbench",
(PyCFunction)ApplicationPy::sRemoveWorkbenchHandler,
METH_VARARGS,
"removeWorkbench(name) -> None\n"
"\n"
"Remove a workbench.\n"
"\n"
"name : str\n Name of the workbench to remove."},
{"getWorkbench",
(PyCFunction)ApplicationPy::sGetWorkbenchHandler,
METH_VARARGS,
"getWorkbench(name) -> Workbench\n"
"\n"
"Get the workbench by its name.\n"
"\n"
"name : str\n Name of the workbench to return."},
{"listWorkbenches",
(PyCFunction)ApplicationPy::sListWorkbenchHandlers,
METH_VARARGS,
"listWorkbenches() -> dict\n"
"\n"
"Get a dictionary with all workbenches."},
{"activeWorkbench",
(PyCFunction)ApplicationPy::sActiveWorkbenchHandler,
METH_VARARGS,
"activeWorkbench() -> Workbench\n"
"\n"
"Return the active workbench object."},
{"addResourcePath",
(PyCFunction)ApplicationPy::sAddResPath,
METH_VARARGS,
"addResourcePath(path) -> None\n"
"\n"
"Add a new path to the system where to find resource files\n"
"like icons or localization files.\n"
"\n"
"path : str, bytes, bytearray\n Path to resource files."},
{"addLanguagePath",
(PyCFunction)ApplicationPy::sAddLangPath,
METH_VARARGS,
"addLanguagePath(path) -> None\n"
"\n"
"Add a new path to the system where to find language files.\n"
"\n"
"path : str, bytes, bytearray\n Path to language files."},
{"addIconPath",
(PyCFunction)ApplicationPy::sAddIconPath,
METH_VARARGS,
"addIconPath(path) -> None\n"
"\n"
"Add a new path to the system where to find icon files.\n"
"\n"
"path : str, bytes, bytearray\n Path to icon files."},
{"addIcon",
(PyCFunction)ApplicationPy::sAddIcon,
METH_VARARGS,
"addIcon(name, content, format='XPM') -> None\n"
"\n"
"Add an icon to the system.\n"
"\n"
"name : str\n Name of the icon.\n"
"content : str, bytes-like\n Content of the icon.\n"
"format : str\n Format of the icon."},
{"getIcon",
(PyCFunction)ApplicationPy::sGetIcon,
METH_VARARGS,
"getIcon(name) -> QIcon or None\n"
"\n"
"Get an icon in the system. If the pixmap is null, return None.\n"
"\n"
"name : str\n Name of the icon."},
{"isIconCached",
(PyCFunction)ApplicationPy::sIsIconCached,
METH_VARARGS,
"isIconCached(name) -> Bool\n"
"\n"
"Check if an icon with the given name is cached.\n"
"\n"
"name : str\n Name of the icon."},
{"getMainWindow",
(PyCFunction)ApplicationPy::sGetMainWindow,
METH_VARARGS,
"getMainWindow() -> QMainWindow\n"
"\n"
"Return the main window instance."},
{"updateGui",
(PyCFunction)ApplicationPy::sUpdateGui,
METH_VARARGS,
"updateGui() -> None\n"
"\n"
"Update the main window and all its windows."},
{"updateLocale",
(PyCFunction)ApplicationPy::sUpdateLocale,
METH_VARARGS,
"updateLocale() -> None\n"
"\n"
"Update the localization."},
{"getLocale",
(PyCFunction)ApplicationPy::sGetLocale,
METH_VARARGS,
"getLocale() -> str\n"
"\n"
"Returns the locale currently used by FreeCAD."},
{"setLocale",
(PyCFunction)ApplicationPy::sSetLocale,
METH_VARARGS,
"setLocale(name) -> None\n"
"\n"
"Sets the locale used by FreeCAD. Can be set by top-level\n"
"domain (e.g. \"de\") or the language name (e.g. \"German\").\n"
"\n"
"name : str\n Locale name."},
{"supportedLocales",
(PyCFunction)ApplicationPy::sSupportedLocales,
METH_VARARGS,
"supportedLocales() -> dict\n"
"\n"
"Returns a dict of all supported locales. The keys are the language\n"
"names and the values the top-level domains."},
{"createDialog",
(PyCFunction)ApplicationPy::sCreateDialog,
METH_VARARGS,
"createDialog(path) -> PyResource\n"
"\n"
"Open a UI file.\n"
"\n"
"path : str\n UI file path."},
{"addPreferencePage",
(PyCFunction)ApplicationPy::sAddPreferencePage,
METH_VARARGS,
"addPreferencePage(path, group) -> None\n"
"addPreferencePage(dialog, group) -> None\n"
"\n"
"Add a UI form to the preferences dialog in the specified group.\n"
"\n"
"path : str\n UI file path.\n"
"group : str\n Group name.\n"
"dialog : type\n Preference page."},
{"addCommand",
(PyCFunction)ApplicationPy::sAddCommand,
METH_VARARGS,
"addCommand(name, cmd, activation) -> None\n"
"\n"
"Add a command object.\n"
"\n"
"name : str\n Name of the command.\n"
"cmd : object\n Command instance.\n"
"activation : str\n Activation sequence. Optional."},
{"runCommand",
(PyCFunction)ApplicationPy::sRunCommand,
METH_VARARGS,
"runCommand(name, index=0) -> None\n"
"\n"
"Run command by its name.\n"
"\n"
"name : str\n Name of the command.\n"
"index : int\n Index of the child command."},
{"SendMsgToActiveView",
(PyCFunction)ApplicationPy::sSendActiveView,
METH_VARARGS,
"SendMsgToActiveView(name, suppress=False) -> str or None\n"
"\n"
"Send message to the active view. Deprecated, use class View.\n"
"\n"
"name : str\n Name of the view command.\n"
"suppress : bool\n If the sent message fail, suppress warning message."},
{"sendMsgToFocusView",
(PyCFunction)ApplicationPy::sSendFocusView,
METH_VARARGS,
"sendMsgToFocusView(name, suppress=False) -> str or None\n"
"\n"
"Send message to the focused view.\n"
"\n"
"name : str\n Name of the view command.\n"
"suppress : bool\n If send message fail, suppress warning message."},
{"hide",
(PyCFunction)ApplicationPy::sHide,
METH_VARARGS,
"hide(name) -> None\n"
"\n"
"Hide the given feature. Deprecated.\n"
"\n"
"name : str\n Feature name."},
{"show",
(PyCFunction)ApplicationPy::sShow,
METH_VARARGS,
"show(name) -> None\n"
"\n"
"Show the given feature. Deprecated.\n"
"\n"
"name : str\n Feature name."},
{"hideObject",
(PyCFunction)ApplicationPy::sHideObject,
METH_VARARGS,
"hideObject(obj) -> None\n"
"\n"
"Hide the view provider of the given object.\n"
"\n"
"obj : App.DocumentObject"},
{"showObject",
(PyCFunction)ApplicationPy::sShowObject,
METH_VARARGS,
"showObject(obj) -> None\n"
"\n"
"Show the view provider of the given object.\n"
"\n"
"obj : App.DocumentObject"},
{"open",
(PyCFunction)ApplicationPy::sOpen,
METH_VARARGS,
"open(fileName) -> None\n"
"\n"
"Open a macro, Inventor or VRML file.\n"
"\n"
"fileName : str, bytes, bytearray\n File name."},
{"insert",
(PyCFunction)ApplicationPy::sInsert,
METH_VARARGS,
"insert(fileName, docName) -> None\n"
"\n"
"Insert a macro, Inventor or VRML file. If no document name\n"
"is given the active document is used.\n"
"\n"
"fileName : str, bytes, bytearray\n File name.\n"
"docName : str\n Document name."},
{"export",
(PyCFunction)ApplicationPy::sExport,
METH_VARARGS,
"export(objs, fileName) -> None\n"
"\n"
"Save scene to Inventor or VRML file.\n"
"\n"
"objs : sequence of App.DocumentObject\n Sequence of objects to save.\n"
"fileName : str, bytes, bytearray\n File name."},
{"activeDocument",
(PyCFunction)ApplicationPy::sActiveDocument,
METH_VARARGS,
"activeDocument() -> Gui.Document or None\n"
"\n"
"Return the active document. If no one exists, return None."},
{"setActiveDocument",
(PyCFunction)ApplicationPy::sSetActiveDocument,
METH_VARARGS,
"setActiveDocument(doc) -> None\n"
"\n"
"Activate the specified document.\n"
"\n"
"doc : str, App.Document\n Document to activate."},
{"activeView",
(PyCFunction)ApplicationPy::sActiveView,
METH_VARARGS,
"activeView(typeName) -> object or None\n"
"\n"
"Return the active view of the active document. If no one\n"
"exists, return None.\n"
"\n"
"typeName : str\n Type name."},
{"activateView",
(PyCFunction)ApplicationPy::sActivateView,
METH_VARARGS,
"activateView(typeName, create=False) -> None\n"
"\n"
"Activate a view of the given type in the active document.\n"
"If a view of this type doesn't exist and create is True, a\n"
"new view of this type is created.\n"
"\n"
"type : str\n Type name.\n"
"create : bool"},
{"editDocument",
(PyCFunction)ApplicationPy::sEditDocument,
METH_VARARGS,
"editDocument() -> Gui.Document or None\n"
"\n"
"Return the current editing document. If no one exists,\n"
"return None."},
{"getDocument",
(PyCFunction)ApplicationPy::sGetDocument,
METH_VARARGS,
"getDocument(doc) -> Gui.Document\n"
"\n"
"Get a document.\n"
"\n"
"doc : str, App.Document\n `App.Document` name or `App.Document` object."},
{"doCommand",
(PyCFunction)ApplicationPy::sDoCommand,
METH_VARARGS,
"doCommand(cmd) -> None\n"
"\n"
"Prints the given string in the python console and runs it.\n"
"\n"
"cmd : str"},
{"doCommandGui",
(PyCFunction)ApplicationPy::sDoCommandGui,
METH_VARARGS,
"doCommandGui(cmd) -> None\n"
"\n"
"Prints the given string in the python console and runs it\n"
"but doesn't record it in macros.\n"
"\n"
"cmd : str"},
{"doCommandEval",
(PyCFunction)ApplicationPy::sDoCommandEval,
METH_VARARGS,
"doCommandEval(cmd) -> PyObject\n"
"\n"
"Runs the given string without showing in the python console or recording in\n"
"macros, and returns the result.\n"
"\n"
"cmd : str"},
{"doCommandSkip",
(PyCFunction)ApplicationPy::sDoCommandSkip,
METH_VARARGS,
"doCommandSkip(cmd) -> None\n"
"\n"
"Record the given string in the Macro but comment it out in the console\n"
"\n"
"cmd : str"},
{"addModule",
(PyCFunction)ApplicationPy::sAddModule,
METH_VARARGS,
"addModule(mod) -> None\n"
"\n"
"Prints the given module import only once in the macro recording.\n"
"\n"
"mod : str"},
{"showDownloads",
(PyCFunction)ApplicationPy::sShowDownloads,
METH_VARARGS,
"showDownloads() -> None\n\n"
"Show the downloads manager window."},
{"showPreferences",
(PyCFunction)ApplicationPy::sShowPreferences,
METH_VARARGS,
"showPreferences(grp, index=0) -> None\n"
"\n"
"Show the preferences window.\n"
"\n"
"grp: str\n Group to show.\n"
"index : int\n Page index."},
{"showPreferencesByName",
(PyCFunction)ApplicationPy::sShowPreferencesByName,
METH_VARARGS,
"showPreferencesByName(grp, pagename) -> None\n"
"\n"
"Show the preferences window.\n"
"\n"
"grp: str\n Group to show.\n"
"pagename : str\n Page to show."},
{"createViewer",
(PyCFunction)ApplicationPy::sCreateViewer,
METH_VARARGS,
"createViewer(views=1, name) -> View3DInventorPy or AbstractSplitViewPy\n"
"\n"
"Show and returns a viewer.\n"
"\n"
"views : int\n If > 1 a `AbstractSplitViewPy` object is returned.\n"
"name : str\n Viewer title."},
{"getMarkerIndex",
(PyCFunction)ApplicationPy::sGetMarkerIndex,
METH_VARARGS,
"getMarkerIndex(marker, size=9) -> int\n"
"\n"
"Get marker index according to marker name and size.\n"
"\n"
"marker : str\n Marker style name.\n"
"size : int\n Marker size."},
{"addDocumentObserver",
(PyCFunction)ApplicationPy::sAddDocObserver,
METH_VARARGS,
"addDocumentObserver(obj) -> None\n"
"\n"
"Add an observer to get notifications about changes on documents.\n"
"\n"
"obj : object"},
{"removeDocumentObserver",
(PyCFunction)ApplicationPy::sRemoveDocObserver,
METH_VARARGS,
"removeDocumentObserver(obj) -> None\n"
"\n"
"Remove an added document observer.\n"
"\n"
"obj : object"},
{"addWorkbenchManipulator",
(PyCFunction)ApplicationPy::sAddWbManipulator,
METH_VARARGS,
"addWorkbenchManipulator(obj) -> None\n"
"\n"
"Add a workbench manipulator to modify a workbench when it is activated.\n"
"\n"
"obj : object"},
{"removeWorkbenchManipulator",
(PyCFunction)ApplicationPy::sRemoveWbManipulator,
METH_VARARGS,
"removeWorkbenchManipulator(obj) -> None\n"
"\n"
"Remove an added workbench manipulator.\n"
"\n"
"obj : object"},
{"listUserEditModes",
(PyCFunction)ApplicationPy::sListUserEditModes,
METH_VARARGS,
"listUserEditModes() -> list\n"
"\n"
"List available user edit modes."},
{"getUserEditMode",
(PyCFunction)ApplicationPy::sGetUserEditMode,
METH_VARARGS,
"getUserEditMode() -> str\n"
"\n"
"Get current user edit mode."},
{"setUserEditMode",
(PyCFunction)ApplicationPy::sSetUserEditMode,
METH_VARARGS,
"setUserEditMode(mode) -> bool\n"
"\n"
"Set user edit mode. Returns True if exists, False otherwise.\n"
"\n"
"mode : str"},
{"reload",
(PyCFunction)ApplicationPy::sReload,
METH_VARARGS,
"reload(name) -> App.Document or None\n"
"\n"
"Reload a partial opened document. If the document is not open,\n"
"return None.\n"
"\n"
"name : str\n `App.Document` name."},
{"loadFile",
(PyCFunction)ApplicationPy::sLoadFile,
METH_VARARGS,
"loadFile(fileName, module) -> None\n"
"\n"
"Loads an arbitrary file by delegating to the given Python module.\n"
"If no module is given it will be determined by the file extension.\n"
"If more than one module can load a file the first one will be taken.\n"
"If no module exists to load the file an exception will be raised.\n"
"\n"
"fileName : str\n"
"module : str"},
{"coinRemoveAllChildren",
(PyCFunction)ApplicationPy::sCoinRemoveAllChildren,
METH_VARARGS,
"coinRemoveAllChildren(node) -> None\n"
"\n"
"Remove all children from a group node.\n"
"\n"
"node : object"},
{"suspendWaitCursor",
(PyCFunction)ApplicationPy::sSuspendWaitCursor,
METH_VARARGS,
"suspendWaitCursor() -> None\n\n"
"Temporarily suspends the application's wait cursor and event filter."},
{"resumeWaitCursor",
(PyCFunction)ApplicationPy::sResumeWaitCursor,
METH_VARARGS,
"resumeWaitCursor() -> None\n\n"
"Resumes the application's wait cursor and event filter."},
{"addOrigin",
(PyCFunction)ApplicationPy::sAddOrigin,
METH_VARARGS,
"addOrigin(obj) -> None\n"
"\n"
"Register a Python origin handler for document operations.\n"
"\n"
"obj : object\n Origin object implementing the FileOrigin interface."},
{"removeOrigin",
(PyCFunction)ApplicationPy::sRemoveOrigin,
METH_VARARGS,
"removeOrigin(obj) -> None\n"
"\n"
"Unregister a Python origin handler.\n"
"\n"
"obj : object\n Origin object to unregister."},
{"getOrigin",
(PyCFunction)ApplicationPy::sGetOrigin,
METH_VARARGS,
"getOrigin(id) -> object or None\n"
"\n"
"Get an origin by its ID.\n"
"\n"
"id : str\n The origin ID."},
{"listOrigins",
(PyCFunction)ApplicationPy::sListOrigins,
METH_VARARGS,
"listOrigins() -> list\n"
"\n"
"Get a list of all registered origin IDs."},
{"activeOrigin",
(PyCFunction)ApplicationPy::sActiveOrigin,
METH_VARARGS,
"activeOrigin() -> object or None\n"
"\n"
"Get the currently active origin."},
{"setActiveOrigin",
(PyCFunction)ApplicationPy::sSetActiveOrigin,
METH_VARARGS,
"setActiveOrigin(id) -> bool\n"
"\n"
"Set the active origin by ID.\n"
"\n"
"id : str\n The origin ID to activate."},
{nullptr, nullptr, 0, nullptr} /* Sentinel */
};
PyObject* Gui::ApplicationPy::sEditDocument(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Document* pcDoc = Application::Instance->editDocument();
if (pcDoc) {
return pcDoc->getPyObject();
}
Py_Return;
}
PyObject* Gui::ApplicationPy::sActiveDocument(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Document* pcDoc = Application::Instance->activeDocument();
if (pcDoc) {
return pcDoc->getPyObject();
}
Py_Return;
}
PyObject* Gui::ApplicationPy::sActiveView(PyObject* /*self*/, PyObject* args)
{
const char* typeName = nullptr;
if (!PyArg_ParseTuple(args, "|s", &typeName)) {
return nullptr;
}
PY_TRY
{
Base::Type type;
if (typeName) {
type = Base::Type::fromName(typeName);
if (type.isBad()) {
PyErr_Format(PyExc_TypeError, "Invalid type '%s'", typeName);
return nullptr;
}
}
Gui::MDIView* mdiView = Application::Instance->activeView();
if (mdiView && (type.isBad() || mdiView->isDerivedFrom(type))) {
auto res = Py::asObject(mdiView->getPyObject());
if (!res.isNone() || !type.isBad()) {
return Py::new_reference_to(res);
}
}
if (type.isBad()) {
type = Gui::View3DInventor::getClassTypeId();
}
Application::Instance->activateView(type, true);
mdiView = Application::Instance->activeView();
if (mdiView) {
return mdiView->getPyObject();
}
Py_Return;
}
PY_CATCH
}
PyObject* Gui::ApplicationPy::sActivateView(PyObject* /*self*/, PyObject* args)
{
char* typeStr = nullptr;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
PyObject* create = Py_False;
if (!PyArg_ParseTuple(args, "sO!", &typeStr, &PyBool_Type, &create)) {
return nullptr;
}
Base::Type type = Base::Type::fromName(typeStr);
Application::Instance->activateView(type, Base::asBoolean(create));
Py_Return;
}
PyObject* Gui::ApplicationPy::sSetActiveDocument(PyObject* /*self*/, PyObject* args)
{
Document* pcDoc = nullptr;
do {
char* pstr = nullptr;
if (PyArg_ParseTuple(args, "s", &pstr)) {
pcDoc = Application::Instance->getDocument(pstr);
if (!pcDoc) {
PyErr_Format(PyExc_NameError, "Unknown document '%s'", pstr);
return nullptr;
}
break;
}
PyErr_Clear();
PyObject* doc = nullptr;
if (PyArg_ParseTuple(args, "O!", &(App::DocumentPy::Type), &doc)) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
pcDoc = Application::Instance->getDocument(
static_cast<App::DocumentPy*>(doc)->getDocumentPtr()
);
if (!pcDoc) {
PyErr_Format(PyExc_KeyError, "Unknown document instance");
return nullptr;
}
break;
}
} while (false);
if (!pcDoc) {
PyErr_SetString(PyExc_TypeError, "Either string or App.Document expected");
return nullptr;
}
if (Application::Instance->activeDocument() != pcDoc) {
Gui::MDIView* view = pcDoc->getActiveView();
getMainWindow()->setActiveWindow(view);
}
Py_Return;
}
PyObject* ApplicationPy::sGetDocument(PyObject* /*self*/, PyObject* args)
{
char* pstr = nullptr;
if (PyArg_ParseTuple(args, "s", &pstr)) {
Document* pcDoc = Application::Instance->getDocument(pstr);
if (!pcDoc) {
PyErr_Format(PyExc_NameError, "Unknown document '%s'", pstr);
return nullptr;
}
return pcDoc->getPyObject();
}
PyErr_Clear();
PyObject* doc = nullptr;
if (PyArg_ParseTuple(args, "O!", &(App::DocumentPy::Type), &doc)) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
Document* pcDoc = Application::Instance->getDocument(
static_cast<App::DocumentPy*>(doc)->getDocumentPtr()
);
if (!pcDoc) {
PyErr_Format(PyExc_KeyError, "Unknown document instance");
return nullptr;
}
return pcDoc->getPyObject();
}
PyErr_SetString(PyExc_TypeError, "Either string or App.Document expected");
return nullptr;
}
PyObject* ApplicationPy::sHide(PyObject* /*self*/, PyObject* args)
{
char* psFeatStr = nullptr;
if (!PyArg_ParseTuple(args, "s;Name of the object to hide has to be given!", &psFeatStr)) {
return nullptr;
}
Document* pcDoc = Application::Instance->activeDocument();
if (pcDoc) {
pcDoc->setHide(psFeatStr);
}
Py_Return;
}
PyObject* ApplicationPy::sShow(PyObject* /*self*/, PyObject* args)
{
char* psFeatStr = nullptr;
if (!PyArg_ParseTuple(args, "s;Name of the object to show has to be given!", &psFeatStr)) {
return nullptr;
}
Document* pcDoc = Application::Instance->activeDocument();
if (pcDoc) {
pcDoc->setShow(psFeatStr);
}
Py_Return;
}
PyObject* ApplicationPy::sHideObject(PyObject* /*self*/, PyObject* args)
{
PyObject* object = nullptr;
if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &object)) {
return nullptr;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
App::DocumentObject* obj = static_cast<App::DocumentObjectPy*>(object)->getDocumentObjectPtr();
Application::Instance->hideViewProvider(obj);
Py_Return;
}
PyObject* ApplicationPy::sShowObject(PyObject* /*self*/, PyObject* args)
{
PyObject* object = nullptr;
if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &object)) {
return nullptr;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
App::DocumentObject* obj = static_cast<App::DocumentObjectPy*>(object)->getDocumentObjectPtr();
Application::Instance->showViewProvider(obj);
Py_Return;
}
PyObject* ApplicationPy::sOpen(PyObject* /*self*/, PyObject* args)
{
// only used to open Python files
char* Name = nullptr;
if (!PyArg_ParseTuple(args, "et", "utf-8", &Name)) {
return nullptr;
}
std::string Utf8Name = std::string(Name);
PyMem_Free(Name);
PY_TRY
{
QString fileName = QString::fromUtf8(Utf8Name.c_str());
FileHandler handler(fileName);
if (!handler.openFile()) {
QString ext = handler.extension();
Base::Console().error("File type '%s' not supported\n", ext.toLatin1().constData());
}
}
PY_CATCH;
Py_Return;
}
PyObject* ApplicationPy::sInsert(PyObject* /*self*/, PyObject* args)
{
char* Name = nullptr;
char* DocName = nullptr;
if (!PyArg_ParseTuple(args, "et|s", "utf-8", &Name, &DocName)) {
return nullptr;
}
std::string Utf8Name = std::string(Name);
PyMem_Free(Name);
PY_TRY
{
QString fileName = QString::fromUtf8(Utf8Name.c_str());
FileHandler handler(fileName);
if (!handler.importFile(std::string(DocName ? DocName : ""))) {
QString ext = handler.extension();
Base::Console().error("File type '%s' not supported\n", ext.toLatin1().constData());
}
}
PY_CATCH;
Py_Return;
}
PyObject* ApplicationPy::sExport(PyObject* /*self*/, PyObject* args)
{
PyObject* object = nullptr;
char* Name = nullptr;
if (!PyArg_ParseTuple(args, "Oet", &object, "utf-8", &Name)) {
return nullptr;
}
std::string Utf8Name = std::string(Name);
PyMem_Free(Name);
PY_TRY
{
App::Document* doc = nullptr;
Py::Sequence list(object);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
PyObject* item = (*it).ptr();
if (PyObject_TypeCheck(item, &(App::DocumentObjectPy::Type))) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
App::DocumentObject* obj
= static_cast<App::DocumentObjectPy*>(item)->getDocumentObjectPtr();
doc = obj->getDocument();
break;
}
}
QString fileName = QString::fromUtf8(Utf8Name.c_str());
QFileInfo fi;
fi.setFile(fileName);
QString ext = fi.suffix().toLower();
if (ext == QLatin1String("iv") || ext == QLatin1String("wrl") || ext == QLatin1String("vrml")
|| ext == QLatin1String("wrz") || ext == QLatin1String("x3d")
|| ext == QLatin1String("x3dz") || ext == QLatin1String("xhtml")) {
// build up the graph
auto sep = new SoSeparator();
sep->ref();
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
PyObject* item = (*it).ptr();
if (PyObject_TypeCheck(item, &(App::DocumentObjectPy::Type))) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
App::DocumentObject* obj
= static_cast<App::DocumentObjectPy*>(item)->getDocumentObjectPtr();
Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(obj);
if (vp) {
sep->addChild(vp->getRoot());
}
}
}
SoGetPrimitiveCountAction action;
action.setCanApproximate(true);
action.apply(sep);
constexpr const int triangleLimit = 100000;
constexpr const int pointLimit = 30000;
constexpr const int lineLimit = 10000;
bool binary = false;
if (action.getTriangleCount() > triangleLimit || action.getPointCount() > pointLimit
|| action.getLineCount() > lineLimit) {
binary = true;
}
SoFCDB::writeToFile(sep, Utf8Name.c_str(), binary);
sep->unref();
}
else if (ext == QLatin1String("pdf")) {
// get the view that belongs to the found document
Gui::Document* gui_doc = Application::Instance->getDocument(doc);
if (gui_doc) {
Gui::MDIView* view = gui_doc->getActiveView();
if (view) {
auto view3d = qobject_cast<View3DInventor*>(view);
if (view3d) {
view3d->viewAll();
}
QPrinter printer(QPrinter::ScreenResolution);
// setPdfVersion sets the printed PDF Version to what is chosen in
// Preferences/Import-Export/PDF more details under:
// https://www.kdab.com/creating-pdfa-documents-qt/
printer.setPdfVersion(Gui::Dialog::DlgSettingsPDF::evaluatePDFVersion());
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(fileName);
printer.setCreator(QString::fromStdString(App::Application::getNameWithVersion()));
view->print(&printer);
}
}
}
else {
Base::Console().error("File type '%s' not supported\n", ext.toLatin1().constData());
}
}
PY_CATCH;
Py_Return;
}
PyObject* ApplicationPy::sSendActiveView(PyObject* /*self*/, PyObject* args)
{
char* psCommandStr = nullptr;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
PyObject* suppress = Py_False;
if (!PyArg_ParseTuple(args, "s|O!", &psCommandStr, &PyBool_Type, &suppress)) {
return nullptr;
}
const char* ppReturn = nullptr;
if (!Application::Instance->sendMsgToActiveView(psCommandStr, &ppReturn)) {
if (!Base::asBoolean(suppress)) {
Base::Console().warning("Unknown view command: %s\n", psCommandStr);
}
}
// Print the return value to the output
if (ppReturn) {
return Py_BuildValue("s", ppReturn);
}
Py_Return;
}
PyObject* ApplicationPy::sSendFocusView(PyObject* /*self*/, PyObject* args)
{
char* psCommandStr = nullptr;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
PyObject* suppress = Py_False;
if (!PyArg_ParseTuple(args, "s|O!", &psCommandStr, &PyBool_Type, &suppress)) {
return nullptr;
}
const char* ppReturn = nullptr;
if (!Application::Instance->sendMsgToFocusView(psCommandStr, &ppReturn)) {
if (!Base::asBoolean(suppress)) {
Base::Console().warning("Unknown view command: %s\n", psCommandStr);
}
}
// Print the return value to the output
if (ppReturn) {
return Py_BuildValue("s", ppReturn);
}
Py_Return;
}
PyObject* ApplicationPy::sGetMainWindow(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
try {
return Py::new_reference_to(MainWindowPy::createWrapper(Gui::getMainWindow()));
}
catch (const Py::Exception&) {
return nullptr;
}
}
PyObject* ApplicationPy::sUpdateGui(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
qApp->processEvents();
Py_Return;
}
PyObject* ApplicationPy::sUpdateLocale(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Translator::instance()->refresh();
Py_Return;
}
PyObject* ApplicationPy::sGetLocale(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
std::string locale = Translator::instance()->activeLanguage();
return PyUnicode_FromString(locale.c_str());
}
PyObject* ApplicationPy::sSetLocale(PyObject* /*self*/, PyObject* args)
{
char* name = nullptr;
if (!PyArg_ParseTuple(args, "s", &name)) {
return nullptr;
}
std::string cname(name);
TStringMap map = Translator::instance()->supportedLocales();
map["English"] = "en";
for (const auto& it : map) {
if (it.first == cname || it.second == cname) {
Translator::instance()->activateLanguage(it.first.c_str());
break;
}
}
Py_Return;
}
PyObject* ApplicationPy::sSupportedLocales(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
TStringMap map = Translator::instance()->supportedLocales();
Py::Dict dict;
dict.setItem(Py::String("English"), Py::String("en"));
for (const auto& it : map) {
Py::String key(it.first);
Py::String val(it.second);
dict.setItem(key, val);
}
return Py::new_reference_to(dict);
}
PyObject* ApplicationPy::sCreateDialog(PyObject* /*self*/, PyObject* args)
{
char* fn = nullptr;
if (!PyArg_ParseTuple(args, "s", &fn)) {
return nullptr;
}
PyObject* pPyResource = nullptr;
try {
pPyResource = new PyResource();
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
static_cast<PyResource*>(pPyResource)->load(fn);
}
catch (const Base::Exception& e) {
PyErr_SetString(PyExc_AssertionError, e.what());
return nullptr;
}
return pPyResource;
}
PyObject* ApplicationPy::sAddPreferencePage(PyObject* /*self*/, PyObject* args)
{
char* fn = nullptr;
char* grp = nullptr;
if (PyArg_ParseTuple(args, "ss", &fn, &grp)) {
QFileInfo fi(QString::fromUtf8(fn));
if (!fi.exists()) {
PyErr_SetString(PyExc_RuntimeError, "UI file does not exist");
return nullptr;
}
// add to the preferences dialog
new PrefPageUiProducer(fn, grp);
Py_Return;
}
PyErr_Clear();
PyObject* dlg = nullptr;
// new style classes
if (PyArg_ParseTuple(args, "O!s", &PyType_Type, &dlg, &grp)) {
// add to the preferences dialog
new PrefPagePyProducer(Py::Object(dlg), grp);
Py_Return;
}
return nullptr;
}
PyObject* ApplicationPy::sActivateWorkbenchHandler(PyObject* /*self*/, PyObject* args)
{
char* psKey = nullptr;
if (!PyArg_ParseTuple(args, "s", &psKey)) {
return nullptr;
}
// search for workbench handler from the dictionary
PyObject* pcWorkbench = PyDict_GetItemString(Application::Instance->_pcWorkbenchDictionary, psKey);
if (!pcWorkbench) {
PyErr_Format(PyExc_KeyError, "No such workbench '%s'", psKey);
return nullptr;
}
try {
bool ok = Application::Instance->activateWorkbench(psKey);
return Py::new_reference_to(Py::Boolean(ok));
}
catch (const Base::Exception& e) {
std::stringstream err;
err << psKey << ": " << e.what();
PyErr_SetString(e.getPyExceptionType(), err.str().c_str());
return nullptr;
}
catch (const XERCES_CPP_NAMESPACE::TranscodingException& e) {
std::stringstream err;
char* pMsg = XERCES_CPP_NAMESPACE::XMLString::transcode(e.getMessage());
err << "Transcoding exception in Xerces-c:\n\n"
<< "Transcoding exception raised in activateWorkbench.\n"
<< "Check if your user configuration file is valid.\n"
<< " Exception message:" << pMsg;
XERCES_CPP_NAMESPACE::XMLString::release(&pMsg);
PyErr_SetString(PyExc_RuntimeError, err.str().c_str());
return nullptr;
}
catch (...) {
std::stringstream err;
err << "Unknown C++ exception raised in activateWorkbench('" << psKey << "')";
PyErr_SetString(Base::PyExc_FC_GeneralError, err.str().c_str());
return nullptr;
}
}
PyObject* ApplicationPy::sAddWorkbenchHandler(PyObject* /*self*/, PyObject* args)
{
PyObject* pcObject = nullptr;
if (!PyArg_ParseTuple(args, "O", &pcObject)) {
return nullptr;
}
try {
// get the class object 'Workbench' from the main module that is expected
// to be base class for all workbench classes
Py::Module module("__main__");
Py::Object baseclass(module.getAttr(std::string("Workbench")));
// check whether it is an instance or class object
Py::Object object(pcObject);
Py::String name;
if (PyObject_IsSubclass(object.ptr(), baseclass.ptr()) == 1) {
// create an instance of this class
name = object.getAttr(std::string("__name__"));
Py::Tuple arg;
Py::Callable creation(object);
object = creation.apply(arg);
}
else if (PyObject_IsInstance(object.ptr(), baseclass.ptr()) == 1) {
// extract the class name of the instance
PyErr_Clear(); // PyObject_IsSubclass set an exception
Py::Object classobj = object.getAttr(std::string("__class__"));
name = classobj.getAttr(std::string("__name__"));
}
else {
PyErr_SetString(
PyExc_TypeError,
"arg must be a subclass or an instance of "
"a subclass of 'Workbench'"
);
return nullptr;
}
// NOLINTBEGIN(bugprone-unused-raii)
// Search for some methods and members without invoking them
Py::Callable(object.getAttr(std::string("Initialize")));
Py::Callable(object.getAttr(std::string("GetClassName")));
// NOLINTEND(bugprone-unused-raii)
std::string item = name.as_std_string("ascii");
PyObject* wb
= PyDict_GetItemString(Application::Instance->_pcWorkbenchDictionary, item.c_str());
if (wb) {
PyErr_Format(PyExc_KeyError, "'%s' already exists.", item.c_str());
return nullptr;
}
PyDict_SetItemString(Application::Instance->_pcWorkbenchDictionary, item.c_str(), object.ptr());
Application::Instance->signalRefreshWorkbenches();
}
catch (const Py::Exception&) {
return nullptr;
}
Py_Return;
}
PyObject* ApplicationPy::sRemoveWorkbenchHandler(PyObject* /*self*/, PyObject* args)
{
char* psKey = nullptr;
if (!PyArg_ParseTuple(args, "s", &psKey)) {
return nullptr;
}
PyObject* wb = PyDict_GetItemString(Application::Instance->_pcWorkbenchDictionary, psKey);
if (!wb) {
PyErr_Format(PyExc_KeyError, "No such workbench '%s'", psKey);
return nullptr;
}
WorkbenchManager::instance()->removeWorkbench(psKey);
PyDict_DelItemString(Application::Instance->_pcWorkbenchDictionary, psKey);
Application::Instance->signalRefreshWorkbenches();
Py_Return;
}
PyObject* ApplicationPy::sGetWorkbenchHandler(PyObject* /*self*/, PyObject* args)
{
char* psKey = nullptr;
if (!PyArg_ParseTuple(args, "s", &psKey)) {
return nullptr;
}
// get the python workbench object from the dictionary
PyObject* pcWorkbench = PyDict_GetItemString(Application::Instance->_pcWorkbenchDictionary, psKey);
if (!pcWorkbench) {
PyErr_Format(PyExc_KeyError, "No such workbench '%s'", psKey);
return nullptr;
}
Py_INCREF(pcWorkbench);
return pcWorkbench;
}
PyObject* ApplicationPy::sListWorkbenchHandlers(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Py_INCREF(Application::Instance->_pcWorkbenchDictionary);
return Application::Instance->_pcWorkbenchDictionary;
}
PyObject* ApplicationPy::sActiveWorkbenchHandler(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Workbench* actWb = WorkbenchManager::instance()->active();
if (!actWb) {
PyErr_SetString(PyExc_AssertionError, "No active workbench\n");
return nullptr;
}
// get the python workbench object from the dictionary
std::string key = actWb->name();
PyObject* pcWorkbench
= PyDict_GetItemString(Application::Instance->_pcWorkbenchDictionary, key.c_str());
if (!pcWorkbench) {
PyErr_Format(PyExc_KeyError, "No such workbench '%s'", key.c_str());
return nullptr;
}
// object get incremented
Py_INCREF(pcWorkbench);
return pcWorkbench;
}
PyObject* ApplicationPy::sAddResPath(PyObject* /*self*/, PyObject* args)
{
char* filePath = nullptr;
if (!PyArg_ParseTuple(args, "et", "utf-8", &filePath)) {
return nullptr;
}
QString path = QString::fromUtf8(filePath);
PyMem_Free(filePath);
if (QDir::isRelativePath(path)) {
// Home path ends with '/'
QString home = QString::fromStdString(App::Application::getHomePath());
path = home + path;
}
BitmapFactory().addPath(path);
Translator::instance()->addPath(path);
Py_Return;
}
PyObject* ApplicationPy::sAddLangPath(PyObject* /*self*/, PyObject* args)
{
char* filePath = nullptr;
if (!PyArg_ParseTuple(args, "et", "utf-8", &filePath)) {
return nullptr;
}
QString path = QString::fromUtf8(filePath);
PyMem_Free(filePath);
if (QDir::isRelativePath(path)) {
// Home path ends with '/'
QString home = QString::fromStdString(App::Application::getHomePath());
path = home + path;
}
Translator::instance()->addPath(path);
Py_Return;
}
PyObject* ApplicationPy::sAddIconPath(PyObject* /*self*/, PyObject* args)
{
char* filePath = nullptr;
if (!PyArg_ParseTuple(args, "et", "utf-8", &filePath)) {
return nullptr;
}
QString path = QString::fromUtf8(filePath);
PyMem_Free(filePath);
if (QDir::isRelativePath(path)) {
// Home path ends with '/'
QString home = QString::fromStdString(App::Application::getHomePath());
path = home + path;
}
BitmapFactory().addPath(path);
Py_Return;
}
PyObject* ApplicationPy::sAddIcon(PyObject* /*self*/, PyObject* args)
{
const char* iconName = nullptr;
Py_buffer content;
const char* format = "XPM";
if (!PyArg_ParseTuple(args, "ss*|s", &iconName, &content, &format)) {
return nullptr;
}
QPixmap icon;
if (BitmapFactory().findPixmapInCache(iconName, icon)) {
PyErr_SetString(PyExc_AssertionError, "Icon with this name already registered");
PyBuffer_Release(&content);
return nullptr;
}
const char* contentStr = static_cast<const char*>(content.buf);
QByteArray ary(contentStr, static_cast<int>(content.len));
icon.loadFromData(ary, format);
if (icon.isNull()) {
QString file = QString::fromUtf8(contentStr, static_cast<int>(content.len));
icon.load(file);
}
PyBuffer_Release(&content);
if (icon.isNull()) {
PyErr_SetString(Base::PyExc_FC_GeneralError, "Invalid icon added to application");
return nullptr;
}
BitmapFactory().addPixmapToCache(iconName, icon);
Py_Return;
}
PyObject* ApplicationPy::sGetIcon(PyObject* /*self*/, PyObject* args)
{
char* iconName = nullptr;
if (!PyArg_ParseTuple(args, "s", &iconName)) {
return nullptr;
}
PythonWrapper wrap;
wrap.loadGuiModule();
wrap.loadWidgetsModule();
auto pixmap = BitmapFactory().pixmap(iconName);
if (!pixmap.isNull()) {
return Py::new_reference_to(wrap.fromQIcon(new QIcon(pixmap)));
}
Py_Return;
}
PyObject* ApplicationPy::sIsIconCached(PyObject* /*self*/, PyObject* args)
{
char* iconName = nullptr;
if (!PyArg_ParseTuple(args, "s", &iconName)) {
return nullptr;
}
QPixmap icon;
return Py::new_reference_to(Py::Boolean(BitmapFactory().findPixmapInCache(iconName, icon)));
}
PyObject* ApplicationPy::sAddCommand(PyObject* /*self*/, PyObject* args)
{
char* pName = nullptr;
char* pSource = nullptr;
PyObject* pcCmdObj = nullptr;
if (!PyArg_ParseTuple(args, "sO|s", &pName, &pcCmdObj, &pSource)) {
return nullptr;
}
// get the call stack to find the Python module name
//
std::string module;
std::string group;
try {
Base::PyGILStateLocker lock;
// Get the filename of the running code by using the low-level sys._getframe() method.
// We use this instead of the `inspect` module (which may actually cause imports to execute
// and can result in a circular import if sAddCommand is being called as part of an import
// statement itself), and the `traceback` module (which cannot access the filename of code
// that is being run through the C interface).
Py::Module sysMod(PyImport_ImportModule("sys"), /*owned=*/true);
Py::Callable getFrame(sysMod.getAttr("_getframe"));
Py::Object callerFrame;
Py::Tuple getFrameArgs(1);
getFrameArgs[0] = Py::Long(0);
callerFrame = getFrame.apply(getFrameArgs);
Py::Object codeObj(callerFrame.getAttr("f_code"));
Py::Object filenameObj(codeObj.getAttr("co_filename"));
std::string filename(Py::String(filenameObj).as_std_string());
Base::FileInfo fi(filename);
// convert backslashes to slashes
filename = fi.filePath();
module = fi.fileNamePure();
// for the group name get the directory name after 'Mod'
boost::regex rx("/Mod/(\\w+)/");
boost::smatch what;
if (boost::regex_search(filename, what, rx)) {
group = what[1];
}
else {
rx = "/Ext/freecad/(\\w+)/";
if (boost::regex_search(filename, what, rx)) {
group = what[1];
}
else {
group = module;
}
}
}
catch (Py::Exception& e) {
e.clear();
}
try {
Base::PyGILStateLocker lock;
Py::Object cmd(pcCmdObj);
if (cmd.hasAttr("GetCommands")) {
Command* cmd = new PythonGroupCommand(pName, pcCmdObj);
if (!module.empty()) {
cmd->setAppModuleName(module.c_str());
}
if (!group.empty()) {
cmd->setGroupName(group.c_str());
}
Application::Instance->commandManager().addCommand(cmd);
}
else {
Command* cmd = new PythonCommand(pName, pcCmdObj, pSource);
if (!module.empty()) {
cmd->setAppModuleName(module.c_str());
}
if (!group.empty()) {
cmd->setGroupName(group.c_str());
}
Application::Instance->commandManager().addCommand(cmd);
}
}
catch (const Base::Exception& e) {
e.setPyException();
return nullptr;
}
catch (...) {
PyErr_SetString(
Base::PyExc_FC_GeneralError,
"Unknown C++ exception raised in ApplicationPy::sAddCommand()"
);
return nullptr;
}
Py_Return;
}
PyObject* ApplicationPy::sRunCommand(PyObject* /*self*/, PyObject* args)
{
char* pName = nullptr;
int item = 0;
if (!PyArg_ParseTuple(args, "s|i", &pName, &item)) {
return nullptr;
}
Gui::Command::LogDisabler d1;
Gui::SelectionLogDisabler d2;
Command* cmd = Application::Instance->commandManager().getCommandByName(pName);
if (cmd) {
cmd->invoke(item);
Py_Return;
}
PyErr_Format(Base::PyExc_FC_GeneralError, "No such command '%s'", pName);
return nullptr;
}
PyObject* ApplicationPy::sDoCommand(PyObject* /*self*/, PyObject* args)
{
char* sCmd = nullptr;
if (!PyArg_ParseTuple(args, "s", &sCmd)) {
return nullptr;
}
Gui::Command::LogDisabler d1;
Gui::SelectionLogDisabler d2;
Gui::Command::printPyCaller();
Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd);
PyObject* module = nullptr;
PyObject* dict = nullptr;
Base::PyGILStateLocker locker;
module = PyImport_AddModule("__main__");
if (!module) {
return nullptr;
}
dict = PyModule_GetDict(module);
if (!dict) {
return nullptr;
}
return PyRun_String(sCmd, Py_file_input, dict, dict);
}
PyObject* ApplicationPy::sDoCommandGui(PyObject* /*self*/, PyObject* args)
{
char* sCmd = nullptr;
if (!PyArg_ParseTuple(args, "s", &sCmd)) {
return nullptr;
}
Gui::Command::LogDisabler d1;
Gui::SelectionLogDisabler d2;
Gui::Command::printPyCaller();
Gui::Application::Instance->macroManager()->addLine(MacroManager::Gui, sCmd);
PyObject* module = nullptr;
PyObject* dict = nullptr;
Base::PyGILStateLocker locker;
module = PyImport_AddModule("__main__");
if (!module) {
return nullptr;
}
dict = PyModule_GetDict(module);
if (!dict) {
return nullptr;
}
return PyRun_String(sCmd, Py_file_input, dict, dict);
}
PyObject* ApplicationPy::sDoCommandEval(PyObject* /*self*/, PyObject* args)
{
char* sCmd = nullptr;
if (!PyArg_ParseTuple(args, "s", &sCmd)) {
return nullptr;
}
Gui::Command::LogDisabler d1;
Gui::SelectionLogDisabler d2;
PyObject* module = nullptr;
PyObject* dict = nullptr;
Base::PyGILStateLocker locker;
module = PyImport_AddModule("__main__");
if (!module) {
return nullptr;
}
dict = PyModule_GetDict(module);
if (!dict) {
return nullptr;
}
return PyRun_String(sCmd, Py_eval_input, dict, dict);
}
PyObject* ApplicationPy::sDoCommandSkip(PyObject* /*self*/, PyObject* args)
{
char* sCmd = nullptr;
if (!PyArg_ParseTuple(args, "s", &sCmd)) {
return nullptr;
}
Gui::Command::LogDisabler d1;
Gui::SelectionLogDisabler d2;
Gui::Command::printPyCaller();
Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd);
return Py::None().ptr();
}
PyObject* ApplicationPy::sAddModule(PyObject* /*self*/, PyObject* args)
{
char* pstr = nullptr;
if (!PyArg_ParseTuple(args, "s", &pstr)) {
return nullptr;
}
try {
Command::addModule(Command::Doc, pstr);
Py_Return;
}
catch (const Base::Exception& e) {
PyErr_SetString(PyExc_ImportError, e.what());
return nullptr;
}
}
PyObject* ApplicationPy::sShowDownloads(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
Gui::Dialog::DownloadManager::getInstance();
Py_Return;
}
PyObject* ApplicationPy::sShowPreferences(PyObject* /*self*/, PyObject* args)
{
char* pstr = nullptr;
int idx = 0;
if (!PyArg_ParseTuple(args, "|si", &pstr, &idx)) {
return nullptr;
}
Gui::Dialog::DlgPreferencesImp cDlg(getMainWindow());
if (pstr) {
cDlg.activateGroupPage(QString::fromUtf8(pstr), idx);
}
WaitCursor wc;
wc.restoreCursor();
cDlg.exec();
wc.setWaitCursor();
Py_Return;
}
PyObject* ApplicationPy::sShowPreferencesByName(PyObject* /*self*/, PyObject* args)
{
char* pstr = nullptr;
const char* prefType = "";
if (!PyArg_ParseTuple(args, "s|s", &pstr, &prefType)) {
return nullptr;
}
Gui::Dialog::DlgPreferencesImp cDlg(getMainWindow());
if (pstr && prefType) {
cDlg.activateGroupPageByPageName(QString::fromUtf8(pstr), QString::fromUtf8(prefType));
}
WaitCursor wc;
wc.restoreCursor();
cDlg.exec();
wc.setWaitCursor();
Py_Return;
}
PyObject* ApplicationPy::sCreateViewer(PyObject* /*self*/, PyObject* args)
{
int num_of_views = 1;
char* title = nullptr;
// if one argument (int) is given
if (!PyArg_ParseTuple(args, "|is", &num_of_views, &title)) {
return nullptr;
}
if (num_of_views <= 0) {
PyErr_Format(PyExc_ValueError, "views must be > 0");
return nullptr;
}
if (num_of_views == 1) {
auto viewer = new View3DInventor(nullptr, nullptr);
if (title) {
viewer->setWindowTitle(QString::fromUtf8(title));
}
Gui::getMainWindow()->addWindow(viewer);
return viewer->getPyObject();
}
else {
auto viewer = new SplitView3DInventor(num_of_views, nullptr, nullptr);
if (title) {
viewer->setWindowTitle(QString::fromUtf8(title));
}
Gui::getMainWindow()->addWindow(viewer);
return viewer->getPyObject();
}
}
PyObject* ApplicationPy::sGetMarkerIndex(PyObject* /*self*/, PyObject* args)
{
constexpr const int defaultSize = 9;
char* pstr {};
int defSize = defaultSize;
if (!PyArg_ParseTuple(args, "s|i", &pstr, &defSize)) {
return nullptr;
}
PY_TRY
{
ParameterGrp::handle const hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/View"
);
// find the appropriate marker style string token
std::string marker_arg = pstr;
std::list<std::pair<std::string, std::string>> markerList = {
{"square", "DIAMOND_FILLED"},
{"cross", "CROSS"},
{"hourglass", "HOURGLASS_FILLED"},
{"plus", "PLUS"},
{"empty", "SQUARE_LINE"},
{"quad", "SQUARE_FILLED"},
{"circle", "CIRCLE_LINE"},
{"default", "CIRCLE_FILLED"}
};
auto findIt = std::find_if(markerList.begin(), markerList.end(), [&marker_arg](const auto& it) {
return marker_arg == it.first || marker_arg == it.second;
});
marker_arg = (findIt != markerList.end() ? findIt->second : "CIRCLE_FILLED");
// get the marker size
auto sizeList = Gui::Inventor::MarkerBitmaps::getSupportedSizes(marker_arg);
if (std::ranges::find(sizeList, defSize) == std::end(sizeList)) {
defSize = defaultSize;
}
return Py_BuildValue("i", Gui::Inventor::MarkerBitmaps::getMarkerIndex(marker_arg, defSize));
}
PY_CATCH;
}
PyObject* ApplicationPy::sReload(PyObject* /*self*/, PyObject* args)
{
const char* name = nullptr;
if (!PyArg_ParseTuple(args, "s", &name)) {
return nullptr;
}
PY_TRY
{
auto doc = Application::Instance->reopen(App::GetApplication().getDocument(name));
if (doc) {
return doc->getPyObject();
}
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sLoadFile(PyObject* /*self*/, PyObject* args)
{
const char* path = "";
const char* mod = "";
if (!PyArg_ParseTuple(args, "s|s", &path, &mod)) {
return nullptr;
}
PY_TRY
{
Base::FileInfo fi(path);
if (!fi.isFile() || !fi.exists()) {
PyErr_Format(PyExc_IOError, "File %s doesn't exist.", path);
return nullptr;
}
std::string module = mod;
if (module.empty()) {
std::string ext = fi.extension();
std::vector<std::string> modules = App::GetApplication().getImportModules(ext.c_str());
if (modules.empty()) {
PyErr_Format(PyExc_IOError, "Filetype %s is not supported.", ext.c_str());
return nullptr;
}
module = modules.front();
}
Application::Instance->open(path, module.c_str());
Py_Return;
}
PY_CATCH
}
PyObject* ApplicationPy::sAddDocObserver(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
DocumentObserverPython::addObserver(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sRemoveDocObserver(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
DocumentObserverPython::removeObserver(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sAddWbManipulator(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
WorkbenchManipulatorPython::installManipulator(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sRemoveWbManipulator(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
WorkbenchManipulatorPython::removeManipulator(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sCoinRemoveAllChildren(PyObject* /*self*/, PyObject* args)
{
PyObject* pynode = nullptr;
if (!PyArg_ParseTuple(args, "O", &pynode)) {
return nullptr;
}
PY_TRY
{
void* ptr = nullptr;
Base::Interpreter().convertSWIGPointerObj("pivy.coin", "_p_SoGroup", pynode, &ptr, 0);
if (!ptr) {
PyErr_SetString(PyExc_RuntimeError, "Conversion of coin.SoGroup failed");
return nullptr;
}
coinRemoveAllChildren(static_cast<SoGroup*>(ptr));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sListUserEditModes(PyObject* /*self*/, PyObject* args)
{
Py::List ret;
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
for (auto const& uem : Application::Instance->listUserEditModes()) {
ret.append(Py::String(uem.second.first));
}
return Py::new_reference_to(ret);
}
PyObject* ApplicationPy::sGetUserEditMode(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
return Py::new_reference_to(Py::String(Application::Instance->getUserEditModeUIStrings().first));
}
PyObject* ApplicationPy::sSetUserEditMode(PyObject* /*self*/, PyObject* args)
{
const char* mode = "";
if (!PyArg_ParseTuple(args, "s", &mode)) {
return nullptr;
}
bool ok = Application::Instance->setUserEditMode(std::string(mode));
return Py::new_reference_to(Py::Boolean(ok));
}
PyObject* ApplicationPy::sSuspendWaitCursor(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
WaitCursor::suspend();
Py_RETURN_NONE;
}
PyObject* ApplicationPy::sResumeWaitCursor(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
WaitCursor::resume();
Py_RETURN_NONE;
}
// Origin management methods
PyObject* ApplicationPy::sAddOrigin(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
FileOriginPython::addOrigin(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sRemoveOrigin(PyObject* /*self*/, PyObject* args)
{
PyObject* o = nullptr;
if (!PyArg_ParseTuple(args, "O", &o)) {
return nullptr;
}
PY_TRY
{
FileOriginPython::removeOrigin(Py::Object(o));
Py_Return;
}
PY_CATCH;
}
PyObject* ApplicationPy::sGetOrigin(PyObject* /*self*/, PyObject* args)
{
const char* originId = nullptr;
if (!PyArg_ParseTuple(args, "s", &originId)) {
return nullptr;
}
PY_TRY
{
FileOrigin* origin = OriginManager::instance()->getOrigin(originId);
if (!origin) {
Py_Return; // Return None if not found
}
// Return origin info as a dictionary
Py::Dict result;
result.setItem("id", Py::String(origin->id()));
result.setItem("name", Py::String(origin->name()));
result.setItem("nickname", Py::String(origin->nickname()));
result.setItem("type", Py::Long(static_cast<int>(origin->type())));
result.setItem("tracksExternally", Py::Boolean(origin->tracksExternally()));
result.setItem("requiresAuthentication", Py::Boolean(origin->requiresAuthentication()));
result.setItem("supportsRevisions", Py::Boolean(origin->supportsRevisions()));
result.setItem("supportsBOM", Py::Boolean(origin->supportsBOM()));
result.setItem("supportsPartNumbers", Py::Boolean(origin->supportsPartNumbers()));
result.setItem("connectionState", Py::Long(static_cast<int>(origin->connectionState())));
return Py::new_reference_to(result);
}
PY_CATCH;
}
PyObject* ApplicationPy::sListOrigins(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
PY_TRY
{
Py::List result;
for (const auto& id : OriginManager::instance()->originIds()) {
result.append(Py::String(id));
}
return Py::new_reference_to(result);
}
PY_CATCH;
}
PyObject* ApplicationPy::sActiveOrigin(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
PY_TRY
{
FileOrigin* origin = OriginManager::instance()->currentOrigin();
if (!origin) {
Py_Return;
}
// Return origin info as a dictionary
Py::Dict result;
result.setItem("id", Py::String(origin->id()));
result.setItem("name", Py::String(origin->name()));
result.setItem("nickname", Py::String(origin->nickname()));
result.setItem("type", Py::Long(static_cast<int>(origin->type())));
result.setItem("tracksExternally", Py::Boolean(origin->tracksExternally()));
result.setItem("requiresAuthentication", Py::Boolean(origin->requiresAuthentication()));
result.setItem("supportsRevisions", Py::Boolean(origin->supportsRevisions()));
result.setItem("supportsBOM", Py::Boolean(origin->supportsBOM()));
result.setItem("supportsPartNumbers", Py::Boolean(origin->supportsPartNumbers()));
result.setItem("connectionState", Py::Long(static_cast<int>(origin->connectionState())));
return Py::new_reference_to(result);
}
PY_CATCH;
}
PyObject* ApplicationPy::sSetActiveOrigin(PyObject* /*self*/, PyObject* args)
{
const char* originId = nullptr;
if (!PyArg_ParseTuple(args, "s", &originId)) {
return nullptr;
}
PY_TRY
{
bool success = OriginManager::instance()->setCurrentOrigin(originId);
return Py::new_reference_to(Py::Boolean(success));
}
PY_CATCH;
}