# FileOriginPython — Python-to-C++ Bridge > **Header:** `src/Gui/FileOriginPython.h` > **Implementation:** `src/Gui/FileOriginPython.cpp` > **Python binding:** `src/Gui/ApplicationPy.cpp` > **Namespace:** `Gui` `FileOriginPython` is an Adapter that wraps a Python object and presents it as a C++ [FileOrigin](./cpp-file-origin.md). This is how Python addons (like SiloOrigin) integrate with the C++ [OriginManager](./cpp-origin-manager.md) without compiling any C++ code. ## Registration from Python The bridge is exposed to Python through three `FreeCADGui` module functions: ```python import FreeCADGui FreeCADGui.addOrigin(obj) # register a Python origin FreeCADGui.removeOrigin(obj) # unregister it FreeCADGui.getOrigin(obj) # look up the wrapper (or None) ``` ### `FreeCADGui.addOrigin(obj)` 1. Checks that `obj` is not already registered (duplicate check via `findOrigin`). 2. Constructs a `FileOriginPython` wrapper around `obj`. 3. Calls `obj.id()` immediately and caches the result — the ID must be non-empty or registration fails. 4. Passes the wrapper to `OriginManager::instance()->registerOrigin(wrapper)`. 5. If `registerOrigin` fails (e.g., duplicate ID), the wrapper is removed from the internal instances list (OriginManager already deleted it). ### `FreeCADGui.removeOrigin(obj)` 1. Finds the wrapper via `findOrigin(obj)`. 2. Removes it from the internal `_instances` vector. 3. Calls `OriginManager::instance()->unregisterOrigin(id)`, which deletes the wrapper. ### Typical Usage (from SiloOrigin) ```python # mods/silo/freecad/silo_origin.py class SiloOrigin: def id(self): return "silo" def name(self): return "Kindred Silo" # ... remaining interface methods ... _silo_origin = SiloOrigin() def register_silo_origin(): FreeCADGui.addOrigin(_silo_origin) def unregister_silo_origin(): FreeCADGui.removeOrigin(_silo_origin) ``` This is called from `InitGui.py` via a deferred `QTimer.singleShot` (1500 ms after GUI init) to ensure the Gui module is fully loaded. ## Method Dispatch Every C++ `FileOrigin` virtual method is implemented by delegating to the corresponding Python method on the wrapped object. Three internal helpers handle the marshalling: | Helper | Signature | Used For | |--------|-----------|----------| | `callStringMethod` | `(name, default) → std::string` | `id()`, `name()`, `nickname()`, `icon()` | | `callBoolMethod` | `(name, default) → bool` | All `supportsXxx()`, `tracksExternally()`, etc. | | `callMethod` | `(name, args) → Py::Object` | Everything else (document ops, state queries) | ### Dispatch Pattern Each overridden method follows the same structure: ``` 1. Acquire GIL ← Base::PyGILStateLocker lock 2. Check if Python object has attr ← _inst.hasAttr("methodName") 3. If missing → return default ← graceful degradation 4. Build Py::Tuple args ← marshal C++ args to Python 5. Call Python method ← func.apply(args) 6. Convert result ← Py::String/Boolean/Long → C++ type 7. Return ← release GIL on scope exit ``` If the Python method is missing, the bridge returns sensible defaults (empty string, `false`, `nullptr`). If the Python method raises an exception, it is caught, reported to the FreeCAD console via `Base::PyException::reportException()`, and the default is returned. ### Document Argument Marshalling Methods that take `App::Document*` convert it to a Python object using: ```cpp Py::Object FileOriginPython::getDocPyObject(App::Document* doc) const { if (!doc) return Py::None(); return Py::asObject(doc->getPyObject()); } ``` This returns the standard `App.Document` Python wrapper so the Python origin can call `doc.Objects`, `doc.FileName`, `doc.save()`, etc. Methods that **return** an `App::Document*` (like `newDocument`, `openDocument`) check the return value with: ```cpp if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) { return static_cast(result.ptr())->getDocumentPtr(); } ``` If the Python method returns `None` or a non-Document object, the bridge returns `nullptr`. ## GIL Management Every method acquires the GIL (`Base::PyGILStateLocker`) before touching any Python objects. This is critical because the C++ origin methods can be called from: - The main Qt event loop (menu clicks, toolbar actions) - `OriginManager::findOwningOrigin()` during document operations - Signal handlers (fastsignals callbacks) All of these may run on the main thread while the GIL is not held. ## Enum Mapping Python origins return integers for enum values: ### `OriginType` (from `type()`) | Python `int` | C++ Enum | |-------------|----------| | `0` | `OriginType::Local` | | `1` | `OriginType::PLM` | | `2` | `OriginType::Cloud` | | `3` | `OriginType::Custom` | ### `ConnectionState` (from `connectionState()`) | Python `int` | C++ Enum | |-------------|----------| | `0` | `ConnectionState::Disconnected` | | `1` | `ConnectionState::Connecting` | | `2` | `ConnectionState::Connected` | | `3` | `ConnectionState::Error` | Out-of-range values fall back to `OriginType::Custom` and `ConnectionState::Connected` respectively. ## Icon Resolution The `icon()` override calls `callStringMethod("icon")` to get a string name, then passes it to `BitmapFactory().iconFromTheme(name)`. The Python origin returns an icon name (e.g., `"silo"`) rather than a QIcon object. ## Required Python Interface Methods the Python object **must** implement (called unconditionally): | Method | Signature | Purpose | |--------|-----------|---------| | `id()` | `→ str` | Unique origin ID (cached on registration) | | `name()` | `→ str` | Display name | | `nickname()` | `→ str` | Short toolbar label | | `icon()` | `→ str` | Icon name for `BitmapFactory` | | `type()` | `→ int` | `OriginType` enum value | | `tracksExternally()` | `→ bool` | Whether origin syncs externally | | `requiresAuthentication()` | `→ bool` | Whether login is needed | | `ownsDocument(doc)` | `→ bool` | Ownership detection | | `documentIdentity(doc)` | `→ str` | Immutable tracking key | | `documentDisplayId(doc)` | `→ str` | Human-readable ID | Methods the Python object **may** implement (checked with `hasAttr`): | Method | Signature | Default | |--------|-----------|---------| | `supportsRevisions()` | `→ bool` | `False` | | `supportsBOM()` | `→ bool` | `False` | | `supportsPartNumbers()` | `→ bool` | `False` | | `supportsAssemblies()` | `→ bool` | `False` | | `connectionState()` | `→ int` | `2` (Connected) | | `connect()` | `→ bool` | `True` | | `disconnect()` | `→ None` | no-op | | `syncProperties(doc)` | `→ bool` | `True` | | `newDocument(name)` | `→ Document` | `None` | | `openDocument(identity)` | `→ Document` | `None` | | `openDocumentInteractive()` | `→ Document` | `None` | | `saveDocument(doc)` | `→ bool` | `False` | | `saveDocumentAs(doc, id)` | `→ bool` | `False` | | `saveDocumentAsInteractive(doc)` | `→ bool` | `False` | | `commitDocument(doc)` | `→ bool` | `False` | | `pullDocument(doc)` | `→ bool` | `False` | | `pushDocument(doc)` | `→ bool` | `False` | | `showInfo(doc)` | `→ None` | no-op | | `showBOM(doc)` | `→ None` | no-op | ## Error Handling All Python exceptions are caught and reported to the FreeCAD console. The bridge **never** propagates a Python exception into C++ — callers always receive a safe default value. ``` [Python exception in SiloOrigin.ownsDocument] → Base::PyException::reportException() → FreeCAD Console: "Python error: ..." → return false ``` This prevents a buggy Python origin from crashing the application. ## Lifetime and Ownership ``` FreeCADGui.addOrigin(py_obj) │ ├─ FileOriginPython(py_obj) ← wraps, holds Py::Object ref │ └─ _inst = py_obj ← prevents Python GC │ ├─ _instances.push_back(wrapper) ← static vector for findOrigin() │ └─ OriginManager::registerOrigin(wrapper) └─ unique_ptr ← OriginManager owns the wrapper ``` - `FileOriginPython` holds a `Py::Object` reference to the Python instance, preventing garbage collection. - `OriginManager` owns the wrapper via `std::unique_ptr`. - The static `_instances` vector provides `findOrigin()` lookup but does **not** own the pointers. - On `removeOrigin()`: the wrapper is removed from `_instances`, then `OriginManager::unregisterOrigin()` deletes the wrapper, which releases the `Py::Object` reference. ## See Also - [FileOrigin Interface](./cpp-file-origin.md) — the abstract interface being adapted - [OriginManager](./cpp-origin-manager.md) — where the wrapped origin gets registered - [CommandOrigin](./cpp-command-origin.md) — commands that dispatch through this bridge - [Creating a Custom Origin (Python)](../guide/custom-origin-python.md) — step-by-step guide using this bridge