diff --git a/mods/sdk/kindred_sdk/context.py b/mods/sdk/kindred_sdk/context.py
index 659105f041..fd61884fa9 100644
--- a/mods/sdk/kindred_sdk/context.py
+++ b/mods/sdk/kindred_sdk/context.py
@@ -1,23 +1,23 @@
"""Editing context and overlay registration wrappers.
-Routes through the ``kcsdk`` C++ module when available, falling back to
-the legacy ``FreeCADGui`` Python bindings for backwards compatibility.
+Routes through the ``kcsdk`` C++ module. The legacy ``FreeCADGui``
+Python bindings are deprecated — kcsdk is required.
"""
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():
- """Lazy import of FreeCADGui (not available in console mode)."""
- import FreeCADGui
-
- return FreeCADGui
+def _require_kcsdk():
+ if _kcsdk is None:
+ raise RuntimeError(
+ "kcsdk module not available. "
+ "The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
+ )
def register_context(context_id, label, color, toolbars, match, priority=50):
@@ -45,13 +45,9 @@ def register_context(context_id, label, color, toolbars, match, priority=50):
if not callable(match):
raise TypeError("match must be callable")
+ _require_kcsdk()
try:
- 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
- )
+ _kcsdk.register_context(context_id, label, color, toolbars, match, priority)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to register context '{context_id}': {e}\n"
@@ -63,11 +59,9 @@ def unregister_context(context_id):
if not isinstance(context_id, str):
raise TypeError(f"context_id must be str, got {type(context_id).__name__}")
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- _kcsdk.unregister_context(context_id)
- else:
- _gui().unregisterEditingContext(context_id)
+ _kcsdk.unregister_context(context_id)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to unregister context '{context_id}': {e}\n"
@@ -96,11 +90,9 @@ def register_overlay(overlay_id, toolbars, match):
if not callable(match):
raise TypeError("match must be callable")
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- _kcsdk.register_overlay(overlay_id, toolbars, match)
- else:
- _gui().registerEditingOverlay(overlay_id, toolbars, match)
+ _kcsdk.register_overlay(overlay_id, toolbars, match)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to register overlay '{overlay_id}': {e}\n"
@@ -112,11 +104,9 @@ def unregister_overlay(overlay_id):
if not isinstance(overlay_id, str):
raise TypeError(f"overlay_id must be str, got {type(overlay_id).__name__}")
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- _kcsdk.unregister_overlay(overlay_id)
- else:
- _gui().unregisterEditingOverlay(overlay_id)
+ _kcsdk.unregister_overlay(overlay_id)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to unregister overlay '{overlay_id}': {e}\n"
@@ -142,11 +132,9 @@ def inject_commands(context_id, toolbar_name, commands):
if not isinstance(commands, list):
raise TypeError(f"commands must be list, got {type(commands).__name__}")
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- _kcsdk.inject_commands(context_id, toolbar_name, commands)
- else:
- _gui().injectEditingCommands(context_id, toolbar_name, commands)
+ _kcsdk.inject_commands(context_id, toolbar_name, commands)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to inject commands into '{context_id}': {e}\n"
@@ -159,10 +147,9 @@ def current_context():
Keys: ``id``, ``label``, ``color``, ``toolbars``, ``breadcrumb``,
``breadcrumbColors``. Returns ``None`` if no context is active.
"""
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- return _kcsdk.current_context()
- return _gui().currentEditingContext()
+ return _kcsdk.current_context()
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kindred_sdk: Failed to get current context: {e}\n"
@@ -172,10 +159,8 @@ def current_context():
def refresh_context():
"""Force re-resolution and update of the editing context."""
+ _require_kcsdk()
try:
- if _kcsdk is not None:
- _kcsdk.refresh()
- else:
- _gui().refreshEditingContext()
+ _kcsdk.refresh()
except Exception as e:
FreeCAD.Console.PrintWarning(f"kindred_sdk: Failed to refresh context: {e}\n")
diff --git a/mods/sdk/kindred_sdk/dock.py b/mods/sdk/kindred_sdk/dock.py
index b2940f8229..168b348a9a 100644
--- a/mods/sdk/kindred_sdk/dock.py
+++ b/mods/sdk/kindred_sdk/dock.py
@@ -1,28 +1,27 @@
"""Dock panel registration helper.
-Routes through the ``kcsdk`` C++ module (IPanelProvider / DockWindowManager)
-when available, falling back to direct PySide QDockWidget creation for
-backwards compatibility.
+Routes through the ``kcsdk`` C++ module (IPanelProvider / DockWindowManager).
+The kcsdk module is required — legacy PySide fallback has been removed.
"""
import FreeCAD
-# Try to import the C++ SDK module; None if not yet built/installed.
try:
import kcsdk as _kcsdk
except ImportError:
_kcsdk = None
-_AREA_MAP = {
- "left": 1, # Qt.LeftDockWidgetArea / DockArea.Left
- "right": 2, # Qt.RightDockWidgetArea / DockArea.Right
- "top": 4, # Qt.TopDockWidgetArea / DockArea.Top
- "bottom": 8, # Qt.BottomDockWidgetArea / DockArea.Bottom
-}
-
_DOCK_AREA_MAP = None # lazily populated from kcsdk
+def _require_kcsdk():
+ if _kcsdk is None:
+ raise RuntimeError(
+ "kcsdk module not available. "
+ "The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
+ )
+
+
def _get_dock_area(area_str):
"""Convert area string to kcsdk.DockArea enum value."""
global _DOCK_AREA_MAP
@@ -36,6 +35,9 @@ def _get_dock_area(area_str):
return _DOCK_AREA_MAP.get(area_str)
+_AREA_NAMES = ("left", "right", "top", "bottom")
+
+
def register_dock_panel(object_name, title, widget_factory, area="right", delay_ms=0):
"""Register a dock panel, optionally deferred.
@@ -59,18 +61,11 @@ def register_dock_panel(object_name, title, widget_factory, area="right", delay_
raise TypeError(f"object_name must be str, got {type(object_name).__name__}")
if not callable(widget_factory):
raise TypeError("widget_factory must be callable")
+ if area not in _AREA_NAMES:
+ raise ValueError(f"area must be one of {list(_AREA_NAMES)}, got {area!r}")
- if area not in _AREA_MAP:
- raise ValueError(f"area must be one of {list(_AREA_MAP)}, got {area!r}")
+ _require_kcsdk()
- if _kcsdk is not None:
- _register_via_kcsdk(object_name, title, widget_factory, area, delay_ms)
- else:
- _register_via_pyside(object_name, title, widget_factory, area, delay_ms)
-
-
-def _register_via_kcsdk(object_name, title, widget_factory, area, delay_ms):
- """Register using the C++ SDK panel provider system."""
dock_area = _get_dock_area(area)
class _AnonymousProvider(_kcsdk.IPanelProvider):
@@ -101,44 +96,6 @@ def _register_via_kcsdk(object_name, title, widget_factory, area, delay_ms):
QTimer.singleShot(max(0, delay_ms), _create)
except Exception as e:
- FreeCAD.Console.PrintLog(
- f"kindred_sdk: kcsdk panel registration failed for '{object_name}', "
- f"falling back: {e}\n"
- )
- _register_via_pyside(object_name, title, widget_factory, area, delay_ms)
-
-
-def _register_via_pyside(object_name, title, widget_factory, area, delay_ms):
- """Legacy fallback: create dock widget directly via PySide."""
- qt_area = _AREA_MAP[area]
-
- def _create():
- try:
- import FreeCADGui
- from PySide import QtCore, QtWidgets
-
- mw = FreeCADGui.getMainWindow()
- if mw is None:
- return
-
- if mw.findChild(QtWidgets.QDockWidget, object_name):
- return
-
- widget = widget_factory()
- panel = QtWidgets.QDockWidget(title, mw)
- panel.setObjectName(object_name)
- panel.setWidget(widget)
- mw.addDockWidget(QtCore.Qt.DockWidgetArea(qt_area), panel)
- except Exception as e:
- FreeCAD.Console.PrintLog(
- f"kindred_sdk: Dock panel '{object_name}' skipped: {e}\n"
- )
-
- try:
- from PySide.QtCore import QTimer
-
- QTimer.singleShot(max(0, delay_ms), _create)
- except Exception as e:
- FreeCAD.Console.PrintLog(
- f"kindred_sdk: Could not schedule dock panel '{object_name}': {e}\n"
+ FreeCAD.Console.PrintWarning(
+ f"kindred_sdk: Panel registration failed for '{object_name}': {e}\n"
)
diff --git a/mods/sdk/kindred_sdk/menu.py b/mods/sdk/kindred_sdk/menu.py
index e6f95164eb..04d9791f13 100644
--- a/mods/sdk/kindred_sdk/menu.py
+++ b/mods/sdk/kindred_sdk/menu.py
@@ -1,53 +1,21 @@
"""Menu provider registration.
-Wraps the C++ ``kcsdk.register_menu()`` API with a Python fallback
-that installs a WorkbenchManipulator to inject menu items.
+Routes through the ``kcsdk`` C++ module. The kcsdk module is required.
"""
-import FreeCAD
-
-
-def _kcsdk_available():
- """Return the kcsdk module if available, else None."""
- try:
- import kcsdk
-
- return kcsdk
- except ImportError:
- return None
-
def register_menu(provider):
"""Register a menu provider for declarative menu placement.
- When the C++ ``kcsdk`` module is available, delegates to its
- ``register_menu()`` which installs a shared WorkbenchManipulator
- that injects items at the specified menu path.
-
- Falls back to installing a Python WorkbenchManipulator directly.
+ Delegates to ``kcsdk.register_menu()`` which installs a shared
+ WorkbenchManipulator that injects items at the specified menu path.
"""
- kcsdk = _kcsdk_available()
- if kcsdk is not None:
- kcsdk.register_menu(provider)
- return
-
- # Fallback: extract data and install a Python manipulator.
- menu_path = provider.menu_path()
- items = provider.items()
-
try:
- import FreeCADGui
-
- class _SDKMenuManipulator:
- def modifyMenuBar(self, menuBar):
- menu_items = [
- "Separator" if item == "separator" else item for item in items
- ]
- FreeCADGui.addCommand # Just to verify we're in GUI mode
- menuBar.appendMenu(menu_path, menu_items)
-
- FreeCADGui.addWorkbenchManipulator(_SDKMenuManipulator())
- except Exception as e:
- FreeCAD.Console.PrintWarning(
- f"kindred_sdk: Failed to register menu '{provider.id()}': {e}\n"
+ import kcsdk
+ except ImportError:
+ raise RuntimeError(
+ "kcsdk module not available. "
+ "The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
)
+
+ kcsdk.register_menu(provider)
diff --git a/mods/sdk/kindred_sdk/origin.py b/mods/sdk/kindred_sdk/origin.py
index 8d19eda1d4..b3e2a2ded2 100644
--- a/mods/sdk/kindred_sdk/origin.py
+++ b/mods/sdk/kindred_sdk/origin.py
@@ -1,17 +1,15 @@
"""FileOrigin registration and query wrappers.
-Wraps ``FreeCADGui.addOrigin()`` / ``removeOrigin()`` with validation
-and error handling. Addons implement the FileOrigin duck-typed
-interface directly (see Silo's ``SiloOrigin`` for the full contract).
+Registration (``register_origin`` / ``unregister_origin``) wraps
+``FreeCADGui.addOrigin()`` / ``removeOrigin()`` with validation.
Query functions (``list_origins``, ``active_origin``, etc.) route
-through the ``kcsdk`` C++ module when available, falling back to
-``FreeCADGui.*`` for backwards compatibility.
+through the ``kcsdk`` C++ module. The kcsdk module is required for
+query operations.
"""
import FreeCAD
-# Try to import the C++ SDK module; None if not yet built/installed.
try:
import kcsdk as _kcsdk
except ImportError:
@@ -21,11 +19,20 @@ _REQUIRED_METHODS = ("id", "name", "type", "ownsDocument")
def _gui():
+ """Lazy import of FreeCADGui (not available in console mode)."""
import FreeCADGui
return FreeCADGui
+def _require_kcsdk():
+ if _kcsdk is None:
+ raise RuntimeError(
+ "kcsdk module not available. "
+ "The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
+ )
+
+
def register_origin(origin):
"""Register a FileOrigin with FreeCADGui.
@@ -60,13 +67,9 @@ def list_origins():
list[str]
Origin IDs (always includes ``"local"``).
"""
- if _kcsdk is not None:
- try:
- return _kcsdk.list_origins()
- except Exception:
- pass
+ _require_kcsdk()
try:
- return _gui().listOrigins()
+ return _kcsdk.list_origins()
except Exception as e:
FreeCAD.Console.PrintWarning(f"kindred_sdk: list_origins failed: {e}\n")
return []
@@ -83,13 +86,9 @@ def active_origin():
``supportsRevisions``, ``supportsBOM``, ``supportsPartNumbers``,
``connectionState``.
"""
- if _kcsdk is not None:
- try:
- return _kcsdk.active_origin()
- except Exception:
- pass
+ _require_kcsdk()
try:
- return _gui().activeOrigin()
+ return _kcsdk.active_origin()
except Exception as e:
FreeCAD.Console.PrintWarning(f"kindred_sdk: active_origin failed: {e}\n")
return None
@@ -108,13 +107,9 @@ def set_active_origin(origin_id):
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
+ _require_kcsdk()
try:
- return _gui().setActiveOrigin(origin_id)
+ return _kcsdk.set_active_origin(origin_id)
except Exception as e:
FreeCAD.Console.PrintWarning(f"kindred_sdk: set_active_origin failed: {e}\n")
return False
@@ -133,13 +128,9 @@ def get_origin(origin_id):
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
+ _require_kcsdk()
try:
- return _gui().getOrigin(origin_id)
+ return _kcsdk.get_origin(origin_id)
except Exception as e:
FreeCAD.Console.PrintWarning(f"kindred_sdk: get_origin failed: {e}\n")
return None
diff --git a/mods/sdk/kindred_sdk/toolbar.py b/mods/sdk/kindred_sdk/toolbar.py
index c14a295626..8774a59669 100644
--- a/mods/sdk/kindred_sdk/toolbar.py
+++ b/mods/sdk/kindred_sdk/toolbar.py
@@ -1,37 +1,21 @@
"""Toolbar provider registration.
-Wraps the C++ ``kcsdk.register_toolbar()`` API with a Python fallback
-that extracts toolbar data and calls ``inject_commands()`` directly.
+Routes through the ``kcsdk`` C++ module. The kcsdk module is required.
"""
-def _kcsdk_available():
- """Return the kcsdk module if available, else None."""
- try:
- import kcsdk
-
- return kcsdk
- except ImportError:
- return None
-
-
def register_toolbar(provider):
"""Register a toolbar provider for automatic context injection.
- When the C++ ``kcsdk`` module is available, delegates to its
- ``register_toolbar()`` which stores the provider and auto-injects
- commands into the target editing contexts.
-
- Falls back to extracting data from the provider and calling
- ``inject_commands()`` directly for each target context.
+ Delegates to ``kcsdk.register_toolbar()`` which stores the provider
+ and auto-injects commands into the target editing contexts.
"""
- kcsdk = _kcsdk_available()
- if kcsdk is not None:
- kcsdk.register_toolbar(provider)
- return
+ try:
+ import kcsdk
+ except ImportError:
+ raise RuntimeError(
+ "kcsdk module not available. "
+ "The kindred_sdk requires the kcsdk C++ module (libKCSDK)."
+ )
- # Fallback: extract data and call inject_commands directly
- from kindred_sdk import inject_commands
-
- for ctx_id in provider.context_ids():
- inject_commands(ctx_id, provider.toolbar_name(), provider.commands())
+ kcsdk.register_toolbar(provider)
diff --git a/mods/sdk/kindred_sdk/version.py b/mods/sdk/kindred_sdk/version.py
index 597e21ba21..983d42c865 100644
--- a/mods/sdk/kindred_sdk/version.py
+++ b/mods/sdk/kindred_sdk/version.py
@@ -1 +1 @@
-SDK_VERSION = "0.1.0"
+SDK_VERSION = "1.0.0"
diff --git a/mods/sdk/package.xml b/mods/sdk/package.xml
index 1577f8702b..da067f02cf 100644
--- a/mods/sdk/package.xml
+++ b/mods/sdk/package.xml
@@ -3,7 +3,7 @@
sdk
Kindred Create addon SDK - stable API for addon integration
- 0.1.0
+ 1.0.0
Kindred Systems
LGPL-2.1-or-later
@@ -17,7 +17,6 @@
0.1.0
0
- true
diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp
index 2472264f84..5dd9229ceb 100644
--- a/src/Gui/ApplicationPy.cpp
+++ b/src/Gui/ApplicationPy.cpp
@@ -2110,6 +2110,7 @@ PyObject* ApplicationPy::sRemoveOrigin(PyObject* /*self*/, PyObject* args)
PyObject* ApplicationPy::sGetOrigin(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.getOrigin() is deprecated. Use kindred_sdk.get_origin() instead.\n");
const char* originId = nullptr;
if (!PyArg_ParseTuple(args, "s", &originId)) {
return nullptr;
@@ -2142,6 +2143,7 @@ PyObject* ApplicationPy::sGetOrigin(PyObject* /*self*/, PyObject* args)
PyObject* ApplicationPy::sListOrigins(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.listOrigins() is deprecated. Use kindred_sdk.list_origins() instead.\n");
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
@@ -2159,6 +2161,7 @@ PyObject* ApplicationPy::sListOrigins(PyObject* /*self*/, PyObject* args)
PyObject* ApplicationPy::sActiveOrigin(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.activeOrigin() is deprecated. Use kindred_sdk.active_origin() instead.\n");
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
@@ -2190,6 +2193,7 @@ PyObject* ApplicationPy::sActiveOrigin(PyObject* /*self*/, PyObject* args)
PyObject* ApplicationPy::sSetActiveOrigin(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.setActiveOrigin() is deprecated. Use kindred_sdk.set_active_origin() instead.\n");
const char* originId = nullptr;
if (!PyArg_ParseTuple(args, "s", &originId)) {
return nullptr;
@@ -2234,6 +2238,7 @@ static PyObject* qStringListToPyList(const QStringList& list)
PyObject* ApplicationPy::sRegisterEditingContext(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.registerEditingContext() is deprecated. Use kindred_sdk.register_context() instead.\n");
const char* id = nullptr;
const char* label = nullptr;
const char* color = nullptr;
@@ -2282,6 +2287,7 @@ PyObject* ApplicationPy::sRegisterEditingContext(PyObject* /*self*/, PyObject* a
PyObject* ApplicationPy::sUnregisterEditingContext(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.unregisterEditingContext() is deprecated. Use kindred_sdk.unregister_context() instead.\n");
const char* id = nullptr;
if (!PyArg_ParseTuple(args, "s", &id)) {
return nullptr;
@@ -2292,6 +2298,7 @@ PyObject* ApplicationPy::sUnregisterEditingContext(PyObject* /*self*/, PyObject*
PyObject* ApplicationPy::sRegisterEditingOverlay(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.registerEditingOverlay() is deprecated. Use kindred_sdk.register_overlay() instead.\n");
const char* id = nullptr;
PyObject* toolbarsList = nullptr;
PyObject* matchCallable = nullptr;
@@ -2333,6 +2340,7 @@ PyObject* ApplicationPy::sRegisterEditingOverlay(PyObject* /*self*/, PyObject* a
PyObject* ApplicationPy::sUnregisterEditingOverlay(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.unregisterEditingOverlay() is deprecated. Use kindred_sdk.unregister_overlay() instead.\n");
const char* id = nullptr;
if (!PyArg_ParseTuple(args, "s", &id)) {
return nullptr;
@@ -2343,6 +2351,7 @@ PyObject* ApplicationPy::sUnregisterEditingOverlay(PyObject* /*self*/, PyObject*
PyObject* ApplicationPy::sInjectEditingCommands(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.injectEditingCommands() is deprecated. Use kindred_sdk.inject_commands() instead.\n");
const char* contextId = nullptr;
const char* toolbarName = nullptr;
PyObject* commandsList = nullptr;
@@ -2359,6 +2368,7 @@ PyObject* ApplicationPy::sInjectEditingCommands(PyObject* /*self*/, PyObject* ar
PyObject* ApplicationPy::sCurrentEditingContext(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.currentEditingContext() is deprecated. Use kindred_sdk.current_context() instead.\n");
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
@@ -2378,6 +2388,7 @@ PyObject* ApplicationPy::sCurrentEditingContext(PyObject* /*self*/, PyObject* ar
PyObject* ApplicationPy::sRefreshEditingContext(PyObject* /*self*/, PyObject* args)
{
+ Base::Console().warning("FreeCADGui.refreshEditingContext() is deprecated. Use kindred_sdk.refresh_context() instead.\n");
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}