feat(sdk): add status bar widget wrapper and origin query bindings (#356)
All checks were successful
Build and Test / build (pull_request) Successful in 29m13s
All checks were successful
Build and Test / build (pull_request) Successful in 29m13s
register_status_widget(): pure Python wrapper that adds a live widget to the main window status bar with context menu discoverability. Origin query bindings (kcsdk.list_origins, active_origin, get_origin, set_active_origin): thin C++ forwarding to OriginManager with Python wrappers using kcsdk-first routing. IOriginProvider and IStatusBarProvider C++ interfaces dropped — existing FileOrigin stack is already complete, and status bar widgets don't need C++ lifecycle management.
This commit is contained in:
@@ -13,28 +13,41 @@ from kindred_sdk.context import (
|
|||||||
)
|
)
|
||||||
from kindred_sdk.dock import register_dock_panel
|
from kindred_sdk.dock import register_dock_panel
|
||||||
from kindred_sdk.menu import register_menu
|
from kindred_sdk.menu import register_menu
|
||||||
from kindred_sdk.origin import register_origin, unregister_origin
|
from kindred_sdk.origin import (
|
||||||
|
active_origin,
|
||||||
|
get_origin,
|
||||||
|
list_origins,
|
||||||
|
register_origin,
|
||||||
|
set_active_origin,
|
||||||
|
unregister_origin,
|
||||||
|
)
|
||||||
|
from kindred_sdk.statusbar import register_status_widget
|
||||||
from kindred_sdk.theme import get_theme_tokens, load_palette
|
from kindred_sdk.theme import get_theme_tokens, load_palette
|
||||||
from kindred_sdk.toolbar import register_toolbar
|
from kindred_sdk.toolbar import register_toolbar
|
||||||
from kindred_sdk.version import SDK_VERSION
|
from kindred_sdk.version import SDK_VERSION
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SDK_VERSION",
|
"SDK_VERSION",
|
||||||
|
"active_origin",
|
||||||
|
"create_version",
|
||||||
|
"current_context",
|
||||||
|
"freecad_version",
|
||||||
|
"get_origin",
|
||||||
|
"get_theme_tokens",
|
||||||
|
"inject_commands",
|
||||||
|
"list_origins",
|
||||||
|
"load_palette",
|
||||||
|
"refresh_context",
|
||||||
"register_command",
|
"register_command",
|
||||||
"register_context",
|
"register_context",
|
||||||
"unregister_context",
|
|
||||||
"register_overlay",
|
|
||||||
"unregister_overlay",
|
|
||||||
"inject_commands",
|
|
||||||
"current_context",
|
|
||||||
"refresh_context",
|
|
||||||
"get_theme_tokens",
|
|
||||||
"load_palette",
|
|
||||||
"register_menu",
|
|
||||||
"register_toolbar",
|
|
||||||
"register_origin",
|
|
||||||
"unregister_origin",
|
|
||||||
"register_dock_panel",
|
"register_dock_panel",
|
||||||
"create_version",
|
"register_menu",
|
||||||
"freecad_version",
|
"register_origin",
|
||||||
|
"register_status_widget",
|
||||||
|
"register_toolbar",
|
||||||
|
"set_active_origin",
|
||||||
|
"unregister_context",
|
||||||
|
"unregister_origin",
|
||||||
|
"unregister_overlay",
|
||||||
|
"register_overlay",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
"""FileOrigin registration wrappers.
|
"""FileOrigin registration and query wrappers.
|
||||||
|
|
||||||
Wraps ``FreeCADGui.addOrigin()`` / ``removeOrigin()`` with validation
|
Wraps ``FreeCADGui.addOrigin()`` / ``removeOrigin()`` with validation
|
||||||
and error handling. Addons implement the FileOrigin duck-typed
|
and error handling. Addons implement the FileOrigin duck-typed
|
||||||
interface directly (see Silo's ``SiloOrigin`` for the full contract).
|
interface directly (see Silo's ``SiloOrigin`` for the full contract).
|
||||||
|
|
||||||
|
Query functions (``list_origins``, ``active_origin``, etc.) route
|
||||||
|
through the ``kcsdk`` C++ module when available, falling back to
|
||||||
|
``FreeCADGui.*`` for backwards compatibility.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
|
||||||
|
# Try to import the C++ SDK module; None if not yet built/installed.
|
||||||
|
try:
|
||||||
|
import kcsdk as _kcsdk
|
||||||
|
except ImportError:
|
||||||
|
_kcsdk = None
|
||||||
|
|
||||||
_REQUIRED_METHODS = ("id", "name", "type", "ownsDocument")
|
_REQUIRED_METHODS = ("id", "name", "type", "ownsDocument")
|
||||||
|
|
||||||
|
|
||||||
@@ -40,3 +50,96 @@ def unregister_origin(origin):
|
|||||||
FreeCAD.Console.PrintLog(f"kindred_sdk: Unregistered origin '{origin.id()}'\n")
|
FreeCAD.Console.PrintLog(f"kindred_sdk: Unregistered origin '{origin.id()}'\n")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to unregister origin: {e}\n")
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to unregister origin: {e}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def list_origins():
|
||||||
|
"""Return IDs of all registered origins.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[str]
|
||||||
|
Origin IDs (always includes ``"local"``).
|
||||||
|
"""
|
||||||
|
if _kcsdk is not None:
|
||||||
|
try:
|
||||||
|
return _kcsdk.list_origins()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return _gui().listOrigins()
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: list_origins failed: {e}\n")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def active_origin():
|
||||||
|
"""Return info about the currently active origin.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict or None
|
||||||
|
Origin info dict with keys: ``id``, ``name``, ``nickname``,
|
||||||
|
``type``, ``tracksExternally``, ``requiresAuthentication``,
|
||||||
|
``supportsRevisions``, ``supportsBOM``, ``supportsPartNumbers``,
|
||||||
|
``connectionState``.
|
||||||
|
"""
|
||||||
|
if _kcsdk is not None:
|
||||||
|
try:
|
||||||
|
return _kcsdk.active_origin()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return _gui().activeOrigin()
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: active_origin failed: {e}\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def set_active_origin(origin_id):
|
||||||
|
"""Set the active origin by ID.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
origin_id : str
|
||||||
|
The origin ID to activate.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if the origin was found and activated.
|
||||||
|
"""
|
||||||
|
if _kcsdk is not None:
|
||||||
|
try:
|
||||||
|
return _kcsdk.set_active_origin(origin_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return _gui().setActiveOrigin(origin_id)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: set_active_origin failed: {e}\n")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_origin(origin_id):
|
||||||
|
"""Get info about a specific origin by ID.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
origin_id : str
|
||||||
|
The origin ID to look up.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict or None
|
||||||
|
Origin info dict, or None if not found.
|
||||||
|
"""
|
||||||
|
if _kcsdk is not None:
|
||||||
|
try:
|
||||||
|
return _kcsdk.get_origin(origin_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return _gui().getOrigin(origin_id)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: get_origin failed: {e}\n")
|
||||||
|
return None
|
||||||
|
|||||||
74
mods/sdk/kindred_sdk/statusbar.py
Normal file
74
mods/sdk/kindred_sdk/statusbar.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""Status bar widget registration wrapper.
|
||||||
|
|
||||||
|
Provides a standardized SDK entry point for adding widgets to the main
|
||||||
|
window's status bar. This is a pure Python wrapper — no C++ interface
|
||||||
|
is needed since the widget is created once and Qt manages its lifecycle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
|
||||||
|
|
||||||
|
def register_status_widget(object_name, label, widget, position="right"):
|
||||||
|
"""Add a widget to the main window status bar.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
object_name : str
|
||||||
|
Qt object name for duplicate prevention.
|
||||||
|
label : str
|
||||||
|
Display name shown in the status bar's right-click context menu
|
||||||
|
(becomes the widget's ``windowTitle``).
|
||||||
|
widget : QWidget
|
||||||
|
The widget instance to add. The caller keeps its own reference
|
||||||
|
for live updates (e.g. connecting signals, updating text).
|
||||||
|
position : str, optional
|
||||||
|
``"left"`` (stretches) or ``"right"`` (permanent, fixed width).
|
||||||
|
Default ``"right"``.
|
||||||
|
"""
|
||||||
|
if not isinstance(object_name, str) or not object_name:
|
||||||
|
raise TypeError("object_name must be a non-empty string")
|
||||||
|
if not isinstance(label, str) or not label:
|
||||||
|
raise TypeError("label must be a non-empty string")
|
||||||
|
if position not in ("left", "right"):
|
||||||
|
raise ValueError(f"position must be 'left' or 'right', got {position!r}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide import QtWidgets
|
||||||
|
|
||||||
|
mw = FreeCADGui.getMainWindow()
|
||||||
|
if mw is None:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
"kindred_sdk: Main window not available, "
|
||||||
|
f"cannot register status widget '{object_name}'\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
sb = mw.statusBar()
|
||||||
|
|
||||||
|
# Duplicate check
|
||||||
|
for child in sb.children():
|
||||||
|
if (
|
||||||
|
isinstance(child, QtWidgets.QWidget)
|
||||||
|
and child.objectName() == object_name
|
||||||
|
):
|
||||||
|
FreeCAD.Console.PrintLog(
|
||||||
|
f"kindred_sdk: Status widget '{object_name}' already exists, skipping\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
widget.setObjectName(object_name)
|
||||||
|
widget.setWindowTitle(label)
|
||||||
|
|
||||||
|
if position == "left":
|
||||||
|
sb.addWidget(widget)
|
||||||
|
else:
|
||||||
|
sb.addPermanentWidget(widget)
|
||||||
|
|
||||||
|
FreeCAD.Console.PrintLog(
|
||||||
|
f"kindred_sdk: Registered status widget '{object_name}'\n"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"kindred_sdk: Failed to register status widget '{object_name}': {e}\n"
|
||||||
|
)
|
||||||
@@ -24,6 +24,7 @@ target_link_libraries(kcsdk_py
|
|||||||
pybind11::module
|
pybind11::module
|
||||||
Python3::Python
|
Python3::Python
|
||||||
KCSDK
|
KCSDK
|
||||||
|
FreeCADGui
|
||||||
)
|
)
|
||||||
|
|
||||||
if(FREECAD_WARN_ERROR)
|
if(FREECAD_WARN_ERROR)
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
#include <Gui/SDK/ThemeEngine.h>
|
#include <Gui/SDK/ThemeEngine.h>
|
||||||
#include <Gui/SDK/Types.h>
|
#include <Gui/SDK/Types.h>
|
||||||
|
|
||||||
|
#include <Gui/FileOrigin.h>
|
||||||
|
#include <Gui/OriginManager.h>
|
||||||
|
|
||||||
#include "PyIMenuProvider.h"
|
#include "PyIMenuProvider.h"
|
||||||
#include "PyIPanelProvider.h"
|
#include "PyIPanelProvider.h"
|
||||||
#include "PyIToolbarProvider.h"
|
#include "PyIToolbarProvider.h"
|
||||||
@@ -47,6 +50,23 @@ using namespace KCSDK;
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// Build a Python dict from a FileOrigin* (same keys as ApplicationPy).
|
||||||
|
py::dict originToDict(Gui::FileOrigin* origin)
|
||||||
|
{
|
||||||
|
py::dict d;
|
||||||
|
d["id"] = origin->id();
|
||||||
|
d["name"] = origin->name();
|
||||||
|
d["nickname"] = origin->nickname();
|
||||||
|
d["type"] = static_cast<int>(origin->type());
|
||||||
|
d["tracksExternally"] = origin->tracksExternally();
|
||||||
|
d["requiresAuthentication"] = origin->requiresAuthentication();
|
||||||
|
d["supportsRevisions"] = origin->supportsRevisions();
|
||||||
|
d["supportsBOM"] = origin->supportsBOM();
|
||||||
|
d["supportsPartNumbers"] = origin->supportsPartNumbers();
|
||||||
|
d["connectionState"] = static_cast<int>(origin->connectionState());
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a ContextSnapshot to a Python dict (same keys as ApplicationPy).
|
/// Convert a ContextSnapshot to a Python dict (same keys as ApplicationPy).
|
||||||
py::dict contextSnapshotToDict(const ContextSnapshot& snap)
|
py::dict contextSnapshotToDict(const ContextSnapshot& snap)
|
||||||
{
|
{
|
||||||
@@ -369,4 +389,53 @@ PYBIND11_MODULE(kcsdk, m)
|
|||||||
},
|
},
|
||||||
py::arg("name") = "catppuccin-mocha",
|
py::arg("name") = "catppuccin-mocha",
|
||||||
"Load a named palette. Returns True on success.");
|
"Load a named palette. Returns True on success.");
|
||||||
|
|
||||||
|
// -- Origin query API ---------------------------------------------------
|
||||||
|
|
||||||
|
m.def("list_origins",
|
||||||
|
[]() {
|
||||||
|
auto* mgr = Gui::OriginManager::instance();
|
||||||
|
return mgr ? mgr->originIds() : std::vector<std::string>{};
|
||||||
|
},
|
||||||
|
"Return IDs of all registered origins.");
|
||||||
|
|
||||||
|
m.def("active_origin",
|
||||||
|
[]() -> py::object {
|
||||||
|
auto* mgr = Gui::OriginManager::instance();
|
||||||
|
if (!mgr) {
|
||||||
|
return py::none();
|
||||||
|
}
|
||||||
|
Gui::FileOrigin* origin = mgr->currentOrigin();
|
||||||
|
if (!origin) {
|
||||||
|
return py::none();
|
||||||
|
}
|
||||||
|
return originToDict(origin);
|
||||||
|
},
|
||||||
|
"Return the active origin as a dict, or None.");
|
||||||
|
|
||||||
|
m.def("set_active_origin",
|
||||||
|
[](const std::string& id) {
|
||||||
|
auto* mgr = Gui::OriginManager::instance();
|
||||||
|
if (!mgr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mgr->setCurrentOrigin(id);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
"Set the active origin by ID. Returns True on success.");
|
||||||
|
|
||||||
|
m.def("get_origin",
|
||||||
|
[](const std::string& id) -> py::object {
|
||||||
|
auto* mgr = Gui::OriginManager::instance();
|
||||||
|
if (!mgr) {
|
||||||
|
return py::none();
|
||||||
|
}
|
||||||
|
Gui::FileOrigin* origin = mgr->getOrigin(id);
|
||||||
|
if (!origin) {
|
||||||
|
return py::none();
|
||||||
|
}
|
||||||
|
return originToDict(origin);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
"Get origin info by ID as a dict, or None if not found.");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user