feat(sdk): per-document origin Python bindings (#391)
All checks were successful
Build and Test / build (pull_request) Successful in 30m22s
All checks were successful
Build and Test / build (pull_request) Successful in 30m22s
Expose the existing C++ per-document origin tracking through the kcsdk pybind11 module and kindred_sdk Python package. New kcsdk functions (accept document name string): - document_origin(doc_name) — get origin via originForDocument() - set_document_origin(doc_name, origin_id) — explicit association - clear_document_origin(doc_name) — clear explicit association - find_owning_origin(doc_name) — ownership detection (no cache) New kindred_sdk wrappers (accept App.Document object): - document_origin(doc) - set_document_origin(doc, origin_id) - clear_document_origin(doc) - find_owning_origin(doc)
This commit is contained in:
@@ -23,10 +23,14 @@ from kindred_sdk.lifecycle import context_history, on_context_enter, on_context_
|
||||
from kindred_sdk.menu import register_menu
|
||||
from kindred_sdk.origin import (
|
||||
active_origin,
|
||||
clear_document_origin,
|
||||
document_origin,
|
||||
find_owning_origin,
|
||||
get_origin,
|
||||
list_origins,
|
||||
register_origin,
|
||||
set_active_origin,
|
||||
set_document_origin,
|
||||
unregister_origin,
|
||||
)
|
||||
from kindred_sdk.registry import (
|
||||
@@ -49,11 +53,14 @@ __all__ = [
|
||||
"addon_resource",
|
||||
"addon_version",
|
||||
"available_contexts",
|
||||
"clear_document_origin",
|
||||
"context_history",
|
||||
"context_stack",
|
||||
"create_version",
|
||||
"current_context",
|
||||
"document_origin",
|
||||
"emit",
|
||||
"find_owning_origin",
|
||||
"freecad_version",
|
||||
"get_origin",
|
||||
"get_theme_tokens",
|
||||
@@ -79,6 +86,7 @@ __all__ = [
|
||||
"remove_breadcrumb_injection",
|
||||
"remove_transition_guard",
|
||||
"set_active_origin",
|
||||
"set_document_origin",
|
||||
"unregister_context",
|
||||
"unregister_origin",
|
||||
"unregister_overlay",
|
||||
|
||||
@@ -28,8 +28,7 @@ def _gui():
|
||||
def _require_kcsdk():
|
||||
if _kcsdk is None:
|
||||
raise RuntimeError(
|
||||
"kcsdk module not available. "
|
||||
"The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
|
||||
"kcsdk module not available. The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
|
||||
)
|
||||
|
||||
|
||||
@@ -134,3 +133,92 @@ def get_origin(origin_id):
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: get_origin failed: {e}\n")
|
||||
return None
|
||||
|
||||
|
||||
def document_origin(doc):
|
||||
"""Get the origin associated with a document.
|
||||
|
||||
Checks explicit association first, then falls back to ownership
|
||||
detection (``ownsDocument``).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
doc : App.Document
|
||||
The document to query.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict or None
|
||||
Origin info dict, or None if no origin is associated.
|
||||
"""
|
||||
_require_kcsdk()
|
||||
try:
|
||||
return _kcsdk.document_origin(doc.Name)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: document_origin failed: {e}\n")
|
||||
return None
|
||||
|
||||
|
||||
def set_document_origin(doc, origin_id):
|
||||
"""Associate a document with a specific origin.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
doc : App.Document
|
||||
The document to associate.
|
||||
origin_id : str
|
||||
The origin ID to associate with the document.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the association was set successfully.
|
||||
"""
|
||||
_require_kcsdk()
|
||||
try:
|
||||
return _kcsdk.set_document_origin(doc.Name, origin_id)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: set_document_origin failed: {e}\n")
|
||||
return False
|
||||
|
||||
|
||||
def clear_document_origin(doc):
|
||||
"""Clear the explicit origin association for a document.
|
||||
|
||||
After clearing, origin queries will fall back to ownership detection.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
doc : App.Document
|
||||
The document to clear the association for.
|
||||
"""
|
||||
_require_kcsdk()
|
||||
try:
|
||||
_kcsdk.clear_document_origin(doc.Name)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: clear_document_origin failed: {e}\n")
|
||||
|
||||
|
||||
def find_owning_origin(doc):
|
||||
"""Find which origin owns a document via ownership detection.
|
||||
|
||||
Unlike ``document_origin``, this bypasses explicit associations and
|
||||
the internal cache — it always queries each registered origin's
|
||||
``ownsDocument`` method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
doc : App.Document
|
||||
The document to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict or None
|
||||
Origin info dict, or None if no origin claims the document.
|
||||
"""
|
||||
_require_kcsdk()
|
||||
try:
|
||||
return _kcsdk.find_owning_origin(doc.Name)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: find_owning_origin failed: {e}\n")
|
||||
return None
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <Gui/SDK/ThemeEngine.h>
|
||||
#include <Gui/SDK/Types.h>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <Gui/FileOrigin.h>
|
||||
#include <Gui/OriginManager.h>
|
||||
|
||||
@@ -110,13 +111,16 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
m.doc() = "KCSDK — Kindred Create addon SDK C++ API";
|
||||
m.attr("API_VERSION_MAJOR") = API_VERSION_MAJOR;
|
||||
|
||||
m.def("available", []() {
|
||||
return SDKRegistry::instance().available();
|
||||
}, "Return names of all registered providers.");
|
||||
m.def(
|
||||
"available",
|
||||
[]() { return SDKRegistry::instance().available(); },
|
||||
"Return names of all registered providers."
|
||||
);
|
||||
|
||||
// -- Editing context API ------------------------------------------------
|
||||
|
||||
m.def("register_context",
|
||||
m.def(
|
||||
"register_context",
|
||||
[](const std::string& id,
|
||||
const std::string& label,
|
||||
const std::string& color,
|
||||
@@ -153,19 +157,19 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"toolbars : list[str]\n Toolbar names to show when active.\n"
|
||||
"match : callable\n Zero-arg callable returning True when active.\n"
|
||||
"priority : int\n Higher values checked first (default 50).\n"
|
||||
"parent_id : str\n Optional parent context ID for hierarchy.");
|
||||
"parent_id : str\n Optional parent context ID for hierarchy."
|
||||
);
|
||||
|
||||
m.def("unregister_context",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterContext(id);
|
||||
},
|
||||
m.def(
|
||||
"unregister_context",
|
||||
[](const std::string& id) { SDKRegistry::instance().unregisterContext(id); },
|
||||
py::arg("id"),
|
||||
"Unregister an editing context.");
|
||||
"Unregister an editing context."
|
||||
);
|
||||
|
||||
m.def("register_overlay",
|
||||
[](const std::string& id,
|
||||
const std::vector<std::string>& toolbars,
|
||||
py::object match) {
|
||||
m.def(
|
||||
"register_overlay",
|
||||
[](const std::string& id, const std::vector<std::string>& toolbars, py::object match) {
|
||||
if (!py::isinstance<py::function>(match) && !py::hasattr(match, "__call__")) {
|
||||
throw py::type_error("match must be callable");
|
||||
}
|
||||
@@ -178,16 +182,18 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
py::arg("id"),
|
||||
py::arg("toolbars"),
|
||||
py::arg("match"),
|
||||
"Register an editing overlay (additive toolbars).");
|
||||
"Register an editing overlay (additive toolbars)."
|
||||
);
|
||||
|
||||
m.def("unregister_overlay",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterOverlay(id);
|
||||
},
|
||||
m.def(
|
||||
"unregister_overlay",
|
||||
[](const std::string& id) { SDKRegistry::instance().unregisterOverlay(id); },
|
||||
py::arg("id"),
|
||||
"Unregister an editing overlay.");
|
||||
"Unregister an editing overlay."
|
||||
);
|
||||
|
||||
m.def("inject_commands",
|
||||
m.def(
|
||||
"inject_commands",
|
||||
[](const std::string& contextId,
|
||||
const std::string& toolbarName,
|
||||
const std::vector<std::string>& commands) {
|
||||
@@ -196,9 +202,11 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
py::arg("context_id"),
|
||||
py::arg("toolbar_name"),
|
||||
py::arg("commands"),
|
||||
"Inject commands into a context's toolbar.");
|
||||
"Inject commands into a context's toolbar."
|
||||
);
|
||||
|
||||
m.def("current_context",
|
||||
m.def(
|
||||
"current_context",
|
||||
[]() -> py::object {
|
||||
ContextSnapshot snap = SDKRegistry::instance().currentContext();
|
||||
if (snap.id.empty()) {
|
||||
@@ -206,15 +214,17 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
}
|
||||
return contextSnapshotToDict(snap);
|
||||
},
|
||||
"Return the current editing context as a dict, or None.");
|
||||
"Return the current editing context as a dict, or None."
|
||||
);
|
||||
|
||||
m.def("refresh",
|
||||
[]() {
|
||||
SDKRegistry::instance().refresh();
|
||||
},
|
||||
"Force re-resolution of the editing context.");
|
||||
m.def(
|
||||
"refresh",
|
||||
[]() { SDKRegistry::instance().refresh(); },
|
||||
"Force re-resolution of the editing context."
|
||||
);
|
||||
|
||||
m.def("available_contexts",
|
||||
m.def(
|
||||
"available_contexts",
|
||||
[]() {
|
||||
auto contexts = SDKRegistry::instance().registeredContexts();
|
||||
py::list result;
|
||||
@@ -231,30 +241,33 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
},
|
||||
"Return metadata for all registered editing contexts.\n\n"
|
||||
"Each entry is a dict with keys: id, parent_id, label_template, color, priority.\n"
|
||||
"Sorted by descending priority (highest first).");
|
||||
"Sorted by descending priority (highest first)."
|
||||
);
|
||||
|
||||
m.def("on_context_changed",
|
||||
m.def(
|
||||
"on_context_changed",
|
||||
[](py::function callback) {
|
||||
auto held = std::make_shared<py::object>(std::move(callback));
|
||||
SDKRegistry::instance().onContextChanged(
|
||||
[held](const ContextSnapshot& snap) {
|
||||
py::gil_scoped_acquire gil;
|
||||
try {
|
||||
(*held)(contextSnapshotToDict(snap));
|
||||
}
|
||||
catch (py::error_already_set& e) {
|
||||
e.discard_as_unraisable(__func__);
|
||||
}
|
||||
});
|
||||
SDKRegistry::instance().onContextChanged([held](const ContextSnapshot& snap) {
|
||||
py::gil_scoped_acquire gil;
|
||||
try {
|
||||
(*held)(contextSnapshotToDict(snap));
|
||||
}
|
||||
catch (py::error_already_set& e) {
|
||||
e.discard_as_unraisable(__func__);
|
||||
}
|
||||
});
|
||||
},
|
||||
py::arg("callback"),
|
||||
"Register a callback for context changes.\n\n"
|
||||
"The callback receives a context dict with keys:\n"
|
||||
"id, label, color, toolbars, breadcrumb, breadcrumbColors, stack.\n"
|
||||
"Called synchronously on the Qt main thread whenever the\n"
|
||||
"editing context changes.");
|
||||
"editing context changes."
|
||||
);
|
||||
|
||||
m.def("context_stack",
|
||||
m.def(
|
||||
"context_stack",
|
||||
[]() -> py::object {
|
||||
ContextSnapshot snap = SDKRegistry::instance().currentContext();
|
||||
if (snap.id.empty()) {
|
||||
@@ -262,27 +275,26 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
}
|
||||
return py::cast(snap.stack);
|
||||
},
|
||||
"Return the current context stack (root to leaf) as a list of IDs.");
|
||||
"Return the current context stack (root to leaf) as a list of IDs."
|
||||
);
|
||||
|
||||
// -- Transition guard API -----------------------------------------------
|
||||
|
||||
m.def("add_transition_guard",
|
||||
m.def(
|
||||
"add_transition_guard",
|
||||
[](py::function callback) -> int {
|
||||
auto held = std::make_shared<py::object>(std::move(callback));
|
||||
SDKRegistry::TransitionGuard guard =
|
||||
[held](const ContextSnapshot& from, const ContextSnapshot& to)
|
||||
-> std::pair<bool, std::string>
|
||||
{
|
||||
SDKRegistry::TransitionGuard guard = [held](
|
||||
const ContextSnapshot& from,
|
||||
const ContextSnapshot& to
|
||||
) -> std::pair<bool, std::string> {
|
||||
py::gil_scoped_acquire gil;
|
||||
try {
|
||||
py::object result = (*held)(
|
||||
contextSnapshotToDict(from),
|
||||
contextSnapshotToDict(to));
|
||||
py::object result = (*held)(contextSnapshotToDict(from), contextSnapshotToDict(to));
|
||||
if (py::isinstance<py::tuple>(result)) {
|
||||
auto tup = result.cast<py::tuple>();
|
||||
bool allowed = tup[0].cast<bool>();
|
||||
std::string reason = tup.size() > 1
|
||||
? tup[1].cast<std::string>() : "";
|
||||
std::string reason = tup.size() > 1 ? tup[1].cast<std::string>() : "";
|
||||
return {allowed, reason};
|
||||
}
|
||||
return {result.cast<bool>(), ""};
|
||||
@@ -298,18 +310,20 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"Register a transition guard.\n\n"
|
||||
"The callback receives (from_ctx, to_ctx) dicts and must return\n"
|
||||
"either a bool or a (bool, reason_str) tuple. Returns a guard ID\n"
|
||||
"for later removal.");
|
||||
"for later removal."
|
||||
);
|
||||
|
||||
m.def("remove_transition_guard",
|
||||
[](int guardId) {
|
||||
SDKRegistry::instance().removeTransitionGuard(guardId);
|
||||
},
|
||||
m.def(
|
||||
"remove_transition_guard",
|
||||
[](int guardId) { SDKRegistry::instance().removeTransitionGuard(guardId); },
|
||||
py::arg("guard_id"),
|
||||
"Remove a previously registered transition guard.");
|
||||
"Remove a previously registered transition guard."
|
||||
);
|
||||
|
||||
// -- Breadcrumb injection API -------------------------------------------
|
||||
|
||||
m.def("inject_breadcrumb",
|
||||
m.def(
|
||||
"inject_breadcrumb",
|
||||
[](const std::string& contextId,
|
||||
const std::vector<std::string>& segments,
|
||||
const std::vector<std::string>& colors) {
|
||||
@@ -317,17 +331,20 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
},
|
||||
py::arg("context_id"),
|
||||
py::arg("segments"),
|
||||
py::arg("colors") = std::vector<std::string>{},
|
||||
py::arg("colors") = std::vector<std::string> {},
|
||||
"Inject additional breadcrumb segments into a context.\n\n"
|
||||
"Segments are appended after the context's own label in the breadcrumb.\n"
|
||||
"Active only when the target context is in the current stack.");
|
||||
"Active only when the target context is in the current stack."
|
||||
);
|
||||
|
||||
m.def("remove_breadcrumb_injection",
|
||||
m.def(
|
||||
"remove_breadcrumb_injection",
|
||||
[](const std::string& contextId) {
|
||||
SDKRegistry::instance().removeBreadcrumbInjection(contextId);
|
||||
},
|
||||
py::arg("context_id"),
|
||||
"Remove a previously injected breadcrumb for a context.");
|
||||
"Remove a previously injected breadcrumb for a context."
|
||||
);
|
||||
|
||||
// -- Enums --------------------------------------------------------------
|
||||
|
||||
@@ -355,7 +372,8 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
.def("persistence", &IPanelProvider::persistence)
|
||||
.def("context_affinity", &IPanelProvider::context_affinity);
|
||||
|
||||
m.def("register_panel",
|
||||
m.def(
|
||||
"register_panel",
|
||||
[](py::object provider) {
|
||||
auto holder = std::make_unique<PyProviderHolder>(std::move(provider));
|
||||
SDKRegistry::instance().registerPanel(std::move(holder));
|
||||
@@ -365,33 +383,34 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"Parameters\n"
|
||||
"----------\n"
|
||||
"provider : IPanelProvider\n"
|
||||
" Panel provider instance implementing id(), title(), create_widget().");
|
||||
" Panel provider instance implementing id(), title(), create_widget()."
|
||||
);
|
||||
|
||||
m.def("unregister_panel",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterPanel(id);
|
||||
},
|
||||
m.def(
|
||||
"unregister_panel",
|
||||
[](const std::string& id) { SDKRegistry::instance().unregisterPanel(id); },
|
||||
py::arg("id"),
|
||||
"Remove a registered panel provider and its dock widget.");
|
||||
"Remove a registered panel provider and its dock widget."
|
||||
);
|
||||
|
||||
m.def("create_panel",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().createPanel(id);
|
||||
},
|
||||
m.def(
|
||||
"create_panel",
|
||||
[](const std::string& id) { SDKRegistry::instance().createPanel(id); },
|
||||
py::arg("id"),
|
||||
"Instantiate the dock widget for a registered panel.");
|
||||
"Instantiate the dock widget for a registered panel."
|
||||
);
|
||||
|
||||
m.def("create_all_panels",
|
||||
[]() {
|
||||
SDKRegistry::instance().createAllPanels();
|
||||
},
|
||||
"Instantiate dock widgets for all registered panels.");
|
||||
m.def(
|
||||
"create_all_panels",
|
||||
[]() { SDKRegistry::instance().createAllPanels(); },
|
||||
"Instantiate dock widgets for all registered panels."
|
||||
);
|
||||
|
||||
m.def("registered_panels",
|
||||
[]() {
|
||||
return SDKRegistry::instance().registeredPanels();
|
||||
},
|
||||
"Return IDs of all registered panel providers.");
|
||||
m.def(
|
||||
"registered_panels",
|
||||
[]() { return SDKRegistry::instance().registeredPanels(); },
|
||||
"Return IDs of all registered panel providers."
|
||||
);
|
||||
|
||||
// -- Toolbar provider API -----------------------------------------------
|
||||
|
||||
@@ -402,7 +421,8 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
.def("context_ids", &IToolbarProvider::context_ids)
|
||||
.def("commands", &IToolbarProvider::commands);
|
||||
|
||||
m.def("register_toolbar",
|
||||
m.def(
|
||||
"register_toolbar",
|
||||
[](py::object provider) {
|
||||
auto holder = std::make_unique<PyToolbarHolder>(std::move(provider));
|
||||
SDKRegistry::instance().registerToolbar(std::move(holder));
|
||||
@@ -412,20 +432,21 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"Parameters\n"
|
||||
"----------\n"
|
||||
"provider : IToolbarProvider\n"
|
||||
" Toolbar provider implementing id(), toolbar_name(), context_ids(), commands().");
|
||||
" Toolbar provider implementing id(), toolbar_name(), context_ids(), commands()."
|
||||
);
|
||||
|
||||
m.def("unregister_toolbar",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterToolbar(id);
|
||||
},
|
||||
m.def(
|
||||
"unregister_toolbar",
|
||||
[](const std::string& id) { SDKRegistry::instance().unregisterToolbar(id); },
|
||||
py::arg("id"),
|
||||
"Remove a registered toolbar provider.");
|
||||
"Remove a registered toolbar provider."
|
||||
);
|
||||
|
||||
m.def("registered_toolbars",
|
||||
[]() {
|
||||
return SDKRegistry::instance().registeredToolbars();
|
||||
},
|
||||
"Return IDs of all registered toolbar providers.");
|
||||
m.def(
|
||||
"registered_toolbars",
|
||||
[]() { return SDKRegistry::instance().registeredToolbars(); },
|
||||
"Return IDs of all registered toolbar providers."
|
||||
);
|
||||
|
||||
// -- Menu provider API --------------------------------------------------
|
||||
|
||||
@@ -436,7 +457,8 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
.def("items", &IMenuProvider::items)
|
||||
.def("context_ids", &IMenuProvider::context_ids);
|
||||
|
||||
m.def("register_menu",
|
||||
m.def(
|
||||
"register_menu",
|
||||
[](py::object provider) {
|
||||
auto holder = std::make_unique<PyMenuHolder>(std::move(provider));
|
||||
SDKRegistry::instance().registerMenu(std::move(holder));
|
||||
@@ -447,24 +469,26 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"----------\n"
|
||||
"provider : IMenuProvider\n"
|
||||
" Menu provider implementing id(), menu_path(), items().\n"
|
||||
" Optionally override context_ids() to limit to specific contexts.");
|
||||
" Optionally override context_ids() to limit to specific contexts."
|
||||
);
|
||||
|
||||
m.def("unregister_menu",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterMenu(id);
|
||||
},
|
||||
m.def(
|
||||
"unregister_menu",
|
||||
[](const std::string& id) { SDKRegistry::instance().unregisterMenu(id); },
|
||||
py::arg("id"),
|
||||
"Remove a registered menu provider.");
|
||||
"Remove a registered menu provider."
|
||||
);
|
||||
|
||||
m.def("registered_menus",
|
||||
[]() {
|
||||
return SDKRegistry::instance().registeredMenus();
|
||||
},
|
||||
"Return IDs of all registered menu providers.");
|
||||
m.def(
|
||||
"registered_menus",
|
||||
[]() { return SDKRegistry::instance().registeredMenus(); },
|
||||
"Return IDs of all registered menu providers."
|
||||
);
|
||||
|
||||
// -- Theme engine API ---------------------------------------------------
|
||||
|
||||
m.def("theme_color",
|
||||
m.def(
|
||||
"theme_color",
|
||||
[](const std::string& token) {
|
||||
auto& engine = ThemeEngine::instance();
|
||||
if (engine.activePaletteName().empty()) {
|
||||
@@ -475,9 +499,11 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
py::arg("token"),
|
||||
"Look up a color by role or name.\n\n"
|
||||
"Returns the hex string (e.g. \"#89b4fa\") or empty string if not found.\n"
|
||||
"Auto-loads the default palette on first call.");
|
||||
"Auto-loads the default palette on first call."
|
||||
);
|
||||
|
||||
m.def("theme_tokens",
|
||||
m.def(
|
||||
"theme_tokens",
|
||||
[]() {
|
||||
auto& engine = ThemeEngine::instance();
|
||||
if (engine.activePaletteName().empty()) {
|
||||
@@ -487,9 +513,11 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
},
|
||||
"Return all color tokens as {name: \"#hex\"}.\n\n"
|
||||
"Includes both raw colors and resolved semantic roles.\n"
|
||||
"Auto-loads the default palette on first call.");
|
||||
"Auto-loads the default palette on first call."
|
||||
);
|
||||
|
||||
m.def("format_qss",
|
||||
m.def(
|
||||
"format_qss",
|
||||
[](const std::string& templateStr) {
|
||||
auto& engine = ThemeEngine::instance();
|
||||
if (engine.activePaletteName().empty()) {
|
||||
@@ -501,25 +529,29 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
"Substitute {token} placeholders in a QSS template.\n\n"
|
||||
"Both raw color names ({blue}) and dotted role names\n"
|
||||
"({accent.primary}) are supported. Unknown tokens are left as-is.\n"
|
||||
"Auto-loads the default palette on first call.");
|
||||
"Auto-loads the default palette on first call."
|
||||
);
|
||||
|
||||
m.def("load_palette",
|
||||
[](const std::string& name) {
|
||||
return ThemeEngine::instance().loadPalette(name);
|
||||
},
|
||||
m.def(
|
||||
"load_palette",
|
||||
[](const std::string& name) { return ThemeEngine::instance().loadPalette(name); },
|
||||
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",
|
||||
m.def(
|
||||
"list_origins",
|
||||
[]() {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
return mgr ? mgr->originIds() : std::vector<std::string>{};
|
||||
return mgr ? mgr->originIds() : std::vector<std::string> {};
|
||||
},
|
||||
"Return IDs of all registered origins.");
|
||||
"Return IDs of all registered origins."
|
||||
);
|
||||
|
||||
m.def("active_origin",
|
||||
m.def(
|
||||
"active_origin",
|
||||
[]() -> py::object {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
@@ -531,9 +563,11 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
}
|
||||
return originToDict(origin);
|
||||
},
|
||||
"Return the active origin as a dict, or None.");
|
||||
"Return the active origin as a dict, or None."
|
||||
);
|
||||
|
||||
m.def("set_active_origin",
|
||||
m.def(
|
||||
"set_active_origin",
|
||||
[](const std::string& id) {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
@@ -542,9 +576,11 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
return mgr->setCurrentOrigin(id);
|
||||
},
|
||||
py::arg("id"),
|
||||
"Set the active origin by ID. Returns True on success.");
|
||||
"Set the active origin by ID. Returns True on success."
|
||||
);
|
||||
|
||||
m.def("get_origin",
|
||||
m.def(
|
||||
"get_origin",
|
||||
[](const std::string& id) -> py::object {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
@@ -557,5 +593,91 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
return originToDict(origin);
|
||||
},
|
||||
py::arg("id"),
|
||||
"Get origin info by ID as a dict, or None if not found.");
|
||||
"Get origin info by ID as a dict, or None if not found."
|
||||
);
|
||||
|
||||
// -- Per-document origin API --------------------------------------------
|
||||
|
||||
m.def(
|
||||
"document_origin",
|
||||
[](const std::string& docName) -> py::object {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
return py::none();
|
||||
}
|
||||
App::Document* doc = App::GetApplication().getDocument(docName.c_str());
|
||||
if (!doc) {
|
||||
return py::none();
|
||||
}
|
||||
Gui::FileOrigin* origin = mgr->originForDocument(doc);
|
||||
if (!origin) {
|
||||
return py::none();
|
||||
}
|
||||
return originToDict(origin);
|
||||
},
|
||||
py::arg("doc_name"),
|
||||
"Get the origin for a document by name. Returns origin dict or None."
|
||||
);
|
||||
|
||||
m.def(
|
||||
"set_document_origin",
|
||||
[](const std::string& docName, const std::string& originId) {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
return false;
|
||||
}
|
||||
App::Document* doc = App::GetApplication().getDocument(docName.c_str());
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
Gui::FileOrigin* origin = originId.empty() ? nullptr : mgr->getOrigin(originId);
|
||||
if (!originId.empty() && !origin) {
|
||||
return false;
|
||||
}
|
||||
mgr->setDocumentOrigin(doc, origin);
|
||||
return true;
|
||||
},
|
||||
py::arg("doc_name"),
|
||||
py::arg("origin_id"),
|
||||
"Associate a document with an origin by ID. Returns True on success."
|
||||
);
|
||||
|
||||
m.def(
|
||||
"clear_document_origin",
|
||||
[](const std::string& docName) {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
return;
|
||||
}
|
||||
App::Document* doc = App::GetApplication().getDocument(docName.c_str());
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
mgr->clearDocumentOrigin(doc);
|
||||
},
|
||||
py::arg("doc_name"),
|
||||
"Clear explicit origin association for a document."
|
||||
);
|
||||
|
||||
m.def(
|
||||
"find_owning_origin",
|
||||
[](const std::string& docName) -> py::object {
|
||||
auto* mgr = Gui::OriginManager::instance();
|
||||
if (!mgr) {
|
||||
return py::none();
|
||||
}
|
||||
App::Document* doc = App::GetApplication().getDocument(docName.c_str());
|
||||
if (!doc) {
|
||||
return py::none();
|
||||
}
|
||||
Gui::FileOrigin* origin = mgr->findOwningOrigin(doc);
|
||||
if (!origin) {
|
||||
return py::none();
|
||||
}
|
||||
return originToDict(origin);
|
||||
},
|
||||
py::arg("doc_name"),
|
||||
"Find which origin owns a document (ownership detection, no cache).\n\n"
|
||||
"Returns origin dict or None."
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user