From 79f69e28565f67a1aa740fad8b40b973422e6afa Mon Sep 17 00:00:00 2001 From: forbes Date: Sun, 1 Mar 2026 14:32:08 -0600 Subject: [PATCH] feat(sdk): remove fallbacks, add deprecation warnings, bump v1.0.0 (#357) - Bump SDK_VERSION to 1.0.0 in version.py and package.xml - Remove tag from package.xml (kcsdk is C++) - Remove all FreeCADGui.* fallback paths in context.py, dock.py, toolbar.py, menu.py; require kcsdk module - Remove query fallbacks in origin.py (keep register/unregister which still need FreeCADGui.addOrigin/removeOrigin) - Add deprecation warnings to 11 superseded FreeCADGui methods in ApplicationPy.cpp (not addOrigin/removeOrigin) - All 39 Tier 1 tests pass --- mods/sdk/kindred_sdk/context.py | 59 +++++++++--------------- mods/sdk/kindred_sdk/dock.py | 79 ++++++++------------------------- mods/sdk/kindred_sdk/menu.py | 52 +++++----------------- mods/sdk/kindred_sdk/origin.py | 51 +++++++++------------ mods/sdk/kindred_sdk/toolbar.py | 38 +++++----------- mods/sdk/kindred_sdk/version.py | 2 +- mods/sdk/package.xml | 3 +- src/Gui/ApplicationPy.cpp | 11 +++++ 8 files changed, 95 insertions(+), 200 deletions(-) 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; }