feat(sdk): migrate editing context API to kcsdk (#351)
Add context/overlay registration, injection, query, and refresh to the KCSDK C++ library and kcsdk pybind11 module. New files: - src/Gui/SDK/Types.h — ContextDef, OverlayDef, ContextSnapshot structs (plain C++, no Qt in public API) Modified: - src/Gui/SDK/SDKRegistry.h/.cpp — register_context/overlay, unregister, inject_commands, current_context, refresh (delegates to EditingContextResolver with std↔Qt conversion) - src/Gui/SDK/CMakeLists.txt — add Types.h, link FreeCADGui - src/Gui/SDK/bindings/kcsdk_py.cpp — bind all context functions with GIL-safe match callable wrapping and dict-based snapshot return - mods/sdk/kindred_sdk/context.py — try kcsdk first, fall back to FreeCADGui for backwards compatibility
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
"""Editing context and overlay registration wrappers.
|
"""Editing context and overlay registration wrappers.
|
||||||
|
|
||||||
Thin wrappers around FreeCADGui editing context bindings. If the
|
Routes through the ``kcsdk`` C++ module when available, falling back to
|
||||||
underlying C++ API changes during an upstream rebase, only this module
|
the legacy ``FreeCADGui`` Python bindings for backwards compatibility.
|
||||||
needs to be updated.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def _gui():
|
def _gui():
|
||||||
"""Lazy import of FreeCADGui (not available in console mode)."""
|
"""Lazy import of FreeCADGui (not available in console mode)."""
|
||||||
@@ -41,7 +46,12 @@ def register_context(context_id, label, color, toolbars, match, priority=50):
|
|||||||
raise TypeError("match must be callable")
|
raise TypeError("match must be callable")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gui().registerEditingContext(context_id, label, color, toolbars, match, priority)
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.register_context(context_id, label, color, toolbars, match, priority)
|
||||||
|
else:
|
||||||
|
_gui().registerEditingContext(
|
||||||
|
context_id, label, color, toolbars, match, priority
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kindred_sdk: Failed to register context '{context_id}': {e}\n"
|
f"kindred_sdk: Failed to register context '{context_id}': {e}\n"
|
||||||
@@ -54,7 +64,10 @@ def unregister_context(context_id):
|
|||||||
raise TypeError(f"context_id must be str, got {type(context_id).__name__}")
|
raise TypeError(f"context_id must be str, got {type(context_id).__name__}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gui().unregisterEditingContext(context_id)
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.unregister_context(context_id)
|
||||||
|
else:
|
||||||
|
_gui().unregisterEditingContext(context_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kindred_sdk: Failed to unregister context '{context_id}': {e}\n"
|
f"kindred_sdk: Failed to unregister context '{context_id}': {e}\n"
|
||||||
@@ -84,7 +97,10 @@ def register_overlay(overlay_id, toolbars, match):
|
|||||||
raise TypeError("match must be callable")
|
raise TypeError("match must be callable")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gui().registerEditingOverlay(overlay_id, toolbars, match)
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.register_overlay(overlay_id, toolbars, match)
|
||||||
|
else:
|
||||||
|
_gui().registerEditingOverlay(overlay_id, toolbars, match)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kindred_sdk: Failed to register overlay '{overlay_id}': {e}\n"
|
f"kindred_sdk: Failed to register overlay '{overlay_id}': {e}\n"
|
||||||
@@ -97,7 +113,10 @@ def unregister_overlay(overlay_id):
|
|||||||
raise TypeError(f"overlay_id must be str, got {type(overlay_id).__name__}")
|
raise TypeError(f"overlay_id must be str, got {type(overlay_id).__name__}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gui().unregisterEditingOverlay(overlay_id)
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.unregister_overlay(overlay_id)
|
||||||
|
else:
|
||||||
|
_gui().unregisterEditingOverlay(overlay_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kindred_sdk: Failed to unregister overlay '{overlay_id}': {e}\n"
|
f"kindred_sdk: Failed to unregister overlay '{overlay_id}': {e}\n"
|
||||||
@@ -124,7 +143,10 @@ def inject_commands(context_id, toolbar_name, commands):
|
|||||||
raise TypeError(f"commands must be list, got {type(commands).__name__}")
|
raise TypeError(f"commands must be list, got {type(commands).__name__}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gui().injectEditingCommands(context_id, toolbar_name, commands)
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.inject_commands(context_id, toolbar_name, commands)
|
||||||
|
else:
|
||||||
|
_gui().injectEditingCommands(context_id, toolbar_name, commands)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kindred_sdk: Failed to inject commands into '{context_id}': {e}\n"
|
f"kindred_sdk: Failed to inject commands into '{context_id}': {e}\n"
|
||||||
@@ -138,15 +160,22 @@ def current_context():
|
|||||||
``breadcrumbColors``. Returns ``None`` if no context is active.
|
``breadcrumbColors``. Returns ``None`` if no context is active.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if _kcsdk is not None:
|
||||||
|
return _kcsdk.current_context()
|
||||||
return _gui().currentEditingContext()
|
return _gui().currentEditingContext()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to get current context: {e}\n")
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"kindred_sdk: Failed to get current context: {e}\n"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def refresh_context():
|
def refresh_context():
|
||||||
"""Force re-resolution and update of the editing context."""
|
"""Force re-resolution and update of the editing context."""
|
||||||
try:
|
try:
|
||||||
_gui().refreshEditingContext()
|
if _kcsdk is not None:
|
||||||
|
_kcsdk.refresh()
|
||||||
|
else:
|
||||||
|
_gui().refreshEditingContext()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to refresh context: {e}\n")
|
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to refresh context: {e}\n")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
set(KCSDK_SRCS
|
set(KCSDK_SRCS
|
||||||
KCSDKGlobal.h
|
KCSDKGlobal.h
|
||||||
|
Types.h
|
||||||
SDKRegistry.h
|
SDKRegistry.h
|
||||||
SDKRegistry.cpp
|
SDKRegistry.cpp
|
||||||
)
|
)
|
||||||
@@ -17,6 +18,7 @@ target_include_directories(KCSDK
|
|||||||
target_link_libraries(KCSDK
|
target_link_libraries(KCSDK
|
||||||
PRIVATE
|
PRIVATE
|
||||||
FreeCADBase
|
FreeCADBase
|
||||||
|
FreeCADGui
|
||||||
)
|
)
|
||||||
|
|
||||||
if(FREECAD_WARN_ERROR)
|
if(FREECAD_WARN_ERROR)
|
||||||
|
|||||||
@@ -23,11 +23,55 @@
|
|||||||
|
|
||||||
#include "SDKRegistry.h"
|
#include "SDKRegistry.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#include <Base/Console.h>
|
#include <Base/Console.h>
|
||||||
|
#include <Gui/EditingContext.h>
|
||||||
|
|
||||||
namespace KCSDK
|
namespace KCSDK
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// -- Helpers: std ↔ Qt conversion (internal) --------------------------------
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
QString toQString(const std::string& s)
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(s.data(), static_cast<int>(s.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fromQString(const QString& s)
|
||||||
|
{
|
||||||
|
QByteArray utf8 = s.toUtf8();
|
||||||
|
return std::string(utf8.constData(), utf8.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList toQStringList(const std::vector<std::string>& v)
|
||||||
|
{
|
||||||
|
QStringList out;
|
||||||
|
out.reserve(static_cast<int>(v.size()));
|
||||||
|
for (const auto& s : v) {
|
||||||
|
out.append(toQString(s));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> fromQStringList(const QStringList& list)
|
||||||
|
{
|
||||||
|
std::vector<std::string> out;
|
||||||
|
out.reserve(list.size());
|
||||||
|
for (const auto& s : list) {
|
||||||
|
out.push_back(fromQString(s));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// -- Singleton --------------------------------------------------------------
|
||||||
|
|
||||||
SDKRegistry& SDKRegistry::instance()
|
SDKRegistry& SDKRegistry::instance()
|
||||||
{
|
{
|
||||||
static SDKRegistry reg;
|
static SDKRegistry reg;
|
||||||
@@ -44,8 +88,70 @@ SDKRegistry::~SDKRegistry() = default;
|
|||||||
std::vector<std::string> SDKRegistry::available() const
|
std::vector<std::string> SDKRegistry::available() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
// No providers registered yet — will be populated in subsequent phases.
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- Editing context API ----------------------------------------------------
|
||||||
|
|
||||||
|
void SDKRegistry::registerContext(const ContextDef& def)
|
||||||
|
{
|
||||||
|
Gui::ContextDefinition guiDef;
|
||||||
|
guiDef.id = toQString(def.id);
|
||||||
|
guiDef.labelTemplate = toQString(def.labelTemplate);
|
||||||
|
guiDef.color = toQString(def.color);
|
||||||
|
guiDef.toolbars = toQStringList(def.toolbars);
|
||||||
|
guiDef.priority = def.priority;
|
||||||
|
guiDef.match = def.match;
|
||||||
|
|
||||||
|
Gui::EditingContextResolver::instance()->registerContext(guiDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDKRegistry::unregisterContext(const std::string& id)
|
||||||
|
{
|
||||||
|
Gui::EditingContextResolver::instance()->unregisterContext(toQString(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDKRegistry::registerOverlay(const OverlayDef& def)
|
||||||
|
{
|
||||||
|
Gui::OverlayDefinition guiDef;
|
||||||
|
guiDef.id = toQString(def.id);
|
||||||
|
guiDef.toolbars = toQStringList(def.toolbars);
|
||||||
|
guiDef.match = def.match;
|
||||||
|
|
||||||
|
Gui::EditingContextResolver::instance()->registerOverlay(guiDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDKRegistry::unregisterOverlay(const std::string& id)
|
||||||
|
{
|
||||||
|
Gui::EditingContextResolver::instance()->unregisterOverlay(toQString(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDKRegistry::injectCommands(const std::string& contextId,
|
||||||
|
const std::string& toolbarName,
|
||||||
|
const std::vector<std::string>& commands)
|
||||||
|
{
|
||||||
|
Gui::EditingContextResolver::instance()->injectCommands(
|
||||||
|
toQString(contextId), toQString(toolbarName), toQStringList(commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextSnapshot SDKRegistry::currentContext() const
|
||||||
|
{
|
||||||
|
Gui::EditingContext ctx =
|
||||||
|
Gui::EditingContextResolver::instance()->currentContext();
|
||||||
|
|
||||||
|
ContextSnapshot snap;
|
||||||
|
snap.id = fromQString(ctx.id);
|
||||||
|
snap.label = fromQString(ctx.label);
|
||||||
|
snap.color = fromQString(ctx.color);
|
||||||
|
snap.toolbars = fromQStringList(ctx.toolbars);
|
||||||
|
snap.breadcrumb = fromQStringList(ctx.breadcrumb);
|
||||||
|
snap.breadcrumbColors = fromQStringList(ctx.breadcrumbColors);
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDKRegistry::refresh()
|
||||||
|
{
|
||||||
|
Gui::EditingContextResolver::instance()->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace KCSDK
|
} // namespace KCSDK
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "KCSDKGlobal.h"
|
#include "KCSDKGlobal.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
namespace KCSDK
|
namespace KCSDK
|
||||||
{
|
{
|
||||||
@@ -55,6 +56,31 @@ public:
|
|||||||
/// Return names of all registered providers (across all provider types).
|
/// Return names of all registered providers (across all provider types).
|
||||||
std::vector<std::string> available() const;
|
std::vector<std::string> available() const;
|
||||||
|
|
||||||
|
// -- Editing context API (delegates to EditingContextResolver) ----------
|
||||||
|
|
||||||
|
/// Register an editing context.
|
||||||
|
void registerContext(const ContextDef& def);
|
||||||
|
|
||||||
|
/// Remove a previously registered editing context.
|
||||||
|
void unregisterContext(const std::string& id);
|
||||||
|
|
||||||
|
/// Register an editing overlay (additive, not exclusive).
|
||||||
|
void registerOverlay(const OverlayDef& def);
|
||||||
|
|
||||||
|
/// Remove a previously registered editing overlay.
|
||||||
|
void unregisterOverlay(const std::string& id);
|
||||||
|
|
||||||
|
/// Inject additional commands into a context's toolbar.
|
||||||
|
void injectCommands(const std::string& contextId,
|
||||||
|
const std::string& toolbarName,
|
||||||
|
const std::vector<std::string>& commands);
|
||||||
|
|
||||||
|
/// Return a snapshot of the current editing context.
|
||||||
|
ContextSnapshot currentContext() const;
|
||||||
|
|
||||||
|
/// Force re-resolution of the editing context.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDKRegistry();
|
SDKRegistry();
|
||||||
|
|
||||||
|
|||||||
68
src/Gui/SDK/Types.h
Normal file
68
src/Gui/SDK/Types.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
/****************************************************************************
|
||||||
|
* *
|
||||||
|
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
|
||||||
|
* *
|
||||||
|
* This file is part of FreeCAD. *
|
||||||
|
* *
|
||||||
|
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||||
|
* under the terms of the GNU Lesser General Public License as *
|
||||||
|
* published by the Free Software Foundation, either version 2.1 of the *
|
||||||
|
* License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* FreeCAD 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 *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with FreeCAD. If not, see *
|
||||||
|
* <https://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifndef KCSDK_TYPES_H
|
||||||
|
#define KCSDK_TYPES_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "KCSDKGlobal.h"
|
||||||
|
|
||||||
|
namespace KCSDK
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Plain-C++ context definition. No Qt types in the public SDK API.
|
||||||
|
struct KCSDKExport ContextDef
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string labelTemplate;
|
||||||
|
std::string color;
|
||||||
|
std::vector<std::string> toolbars;
|
||||||
|
int priority = 50;
|
||||||
|
std::function<bool()> match;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Plain-C++ overlay definition.
|
||||||
|
struct KCSDKExport OverlayDef
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::vector<std::string> toolbars;
|
||||||
|
std::function<bool()> match;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Snapshot of the resolved editing context (read-only).
|
||||||
|
struct KCSDKExport ContextSnapshot
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string label;
|
||||||
|
std::string color;
|
||||||
|
std::vector<std::string> toolbars;
|
||||||
|
std::vector<std::string> breadcrumb;
|
||||||
|
std::vector<std::string> breadcrumbColors;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace KCSDK
|
||||||
|
|
||||||
|
#endif // KCSDK_TYPES_H
|
||||||
@@ -23,12 +23,56 @@
|
|||||||
|
|
||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
#include <pybind11/stl.h>
|
#include <pybind11/stl.h>
|
||||||
|
#include <pybind11/functional.h>
|
||||||
|
|
||||||
#include <Gui/SDK/SDKRegistry.h>
|
#include <Gui/SDK/SDKRegistry.h>
|
||||||
|
#include <Gui/SDK/Types.h>
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
using namespace KCSDK;
|
using namespace KCSDK;
|
||||||
|
|
||||||
|
// -- Dict conversion helpers ------------------------------------------------
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Convert a ContextSnapshot to a Python dict (same keys as ApplicationPy).
|
||||||
|
py::dict contextSnapshotToDict(const ContextSnapshot& snap)
|
||||||
|
{
|
||||||
|
py::dict d;
|
||||||
|
d["id"] = snap.id;
|
||||||
|
d["label"] = snap.label;
|
||||||
|
d["color"] = snap.color;
|
||||||
|
d["toolbars"] = snap.toolbars;
|
||||||
|
d["breadcrumb"] = snap.breadcrumb;
|
||||||
|
d["breadcrumbColors"] = snap.breadcrumbColors;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap a Python callable as a GIL-safe std::function<bool()>.
|
||||||
|
/// The py::object is copied (reference counted) so it survives beyond the
|
||||||
|
/// calling scope. The GIL is acquired before every invocation.
|
||||||
|
std::function<bool()> wrapMatchCallable(py::object pyCallable)
|
||||||
|
{
|
||||||
|
// Copy the py::object to prevent GC.
|
||||||
|
auto held = std::make_shared<py::object>(std::move(pyCallable));
|
||||||
|
return [held]() -> bool {
|
||||||
|
py::gil_scoped_acquire gil;
|
||||||
|
try {
|
||||||
|
py::object result = (*held)();
|
||||||
|
return result.cast<bool>();
|
||||||
|
}
|
||||||
|
catch (py::error_already_set& e) {
|
||||||
|
e.discard_as_unraisable(__func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// -- Module -----------------------------------------------------------------
|
||||||
|
|
||||||
PYBIND11_MODULE(kcsdk, m)
|
PYBIND11_MODULE(kcsdk, m)
|
||||||
{
|
{
|
||||||
m.doc() = "KCSDK — Kindred Create addon SDK C++ API";
|
m.doc() = "KCSDK — Kindred Create addon SDK C++ API";
|
||||||
@@ -37,4 +81,100 @@ PYBIND11_MODULE(kcsdk, m)
|
|||||||
m.def("available", []() {
|
m.def("available", []() {
|
||||||
return SDKRegistry::instance().available();
|
return SDKRegistry::instance().available();
|
||||||
}, "Return names of all registered providers.");
|
}, "Return names of all registered providers.");
|
||||||
|
|
||||||
|
// -- Editing context API ------------------------------------------------
|
||||||
|
|
||||||
|
m.def("register_context",
|
||||||
|
[](const std::string& id,
|
||||||
|
const std::string& label,
|
||||||
|
const std::string& color,
|
||||||
|
const std::vector<std::string>& toolbars,
|
||||||
|
py::object match,
|
||||||
|
int priority) {
|
||||||
|
if (!py::isinstance<py::function>(match) && !py::hasattr(match, "__call__")) {
|
||||||
|
throw py::type_error("match must be callable");
|
||||||
|
}
|
||||||
|
ContextDef def;
|
||||||
|
def.id = id;
|
||||||
|
def.labelTemplate = label;
|
||||||
|
def.color = color;
|
||||||
|
def.toolbars = toolbars;
|
||||||
|
def.priority = priority;
|
||||||
|
def.match = wrapMatchCallable(std::move(match));
|
||||||
|
SDKRegistry::instance().registerContext(def);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
py::arg("label"),
|
||||||
|
py::arg("color"),
|
||||||
|
py::arg("toolbars"),
|
||||||
|
py::arg("match"),
|
||||||
|
py::arg("priority") = 50,
|
||||||
|
"Register an editing context.\n\n"
|
||||||
|
"Parameters\n"
|
||||||
|
"----------\n"
|
||||||
|
"id : str\n Unique context identifier.\n"
|
||||||
|
"label : str\n Display label template ({name} placeholder).\n"
|
||||||
|
"color : str\n Hex color for breadcrumb.\n"
|
||||||
|
"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).");
|
||||||
|
|
||||||
|
m.def("unregister_context",
|
||||||
|
[](const std::string& id) {
|
||||||
|
SDKRegistry::instance().unregisterContext(id);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
"Unregister an editing context.");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
OverlayDef def;
|
||||||
|
def.id = id;
|
||||||
|
def.toolbars = toolbars;
|
||||||
|
def.match = wrapMatchCallable(std::move(match));
|
||||||
|
SDKRegistry::instance().registerOverlay(def);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
py::arg("toolbars"),
|
||||||
|
py::arg("match"),
|
||||||
|
"Register an editing overlay (additive toolbars).");
|
||||||
|
|
||||||
|
m.def("unregister_overlay",
|
||||||
|
[](const std::string& id) {
|
||||||
|
SDKRegistry::instance().unregisterOverlay(id);
|
||||||
|
},
|
||||||
|
py::arg("id"),
|
||||||
|
"Unregister an editing overlay.");
|
||||||
|
|
||||||
|
m.def("inject_commands",
|
||||||
|
[](const std::string& contextId,
|
||||||
|
const std::string& toolbarName,
|
||||||
|
const std::vector<std::string>& commands) {
|
||||||
|
SDKRegistry::instance().injectCommands(contextId, toolbarName, commands);
|
||||||
|
},
|
||||||
|
py::arg("context_id"),
|
||||||
|
py::arg("toolbar_name"),
|
||||||
|
py::arg("commands"),
|
||||||
|
"Inject commands into a context's toolbar.");
|
||||||
|
|
||||||
|
m.def("current_context",
|
||||||
|
[]() -> py::object {
|
||||||
|
ContextSnapshot snap = SDKRegistry::instance().currentContext();
|
||||||
|
if (snap.id.empty()) {
|
||||||
|
return py::none();
|
||||||
|
}
|
||||||
|
return contextSnapshotToDict(snap);
|
||||||
|
},
|
||||||
|
"Return the current editing context as a dict, or None.");
|
||||||
|
|
||||||
|
m.def("refresh",
|
||||||
|
[]() {
|
||||||
|
SDKRegistry::instance().refresh();
|
||||||
|
},
|
||||||
|
"Force re-resolution of the editing context.");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user