Files
create/mods/sdk/kindred_sdk/menu.py
forbes 747c458e23 feat(sdk): add IMenuProvider interface and register_command wrapper (#355)
IMenuProvider: declarative menu placement with optional context awareness.
C++ interface with pybind11 bindings + GIL-safe holder. SDKMenuManipulator
(shared WorkbenchManipulator) injects menu items on workbench switch,
filtered by editing context when context_ids() is non-empty.

register_command(): thin Python wrapper around FreeCADGui.addCommand()
that standardizes the calling convention within the SDK contract.

Python wrappers (kindred_sdk.register_menu, kindred_sdk.register_command)
use kcsdk-first routing with FreeCADGui fallback.
2026-03-01 14:42:00 -06:00

54 lines
1.5 KiB
Python

"""Menu provider registration.
Wraps the C++ ``kcsdk.register_menu()`` API with a Python fallback
that installs a WorkbenchManipulator to inject menu items.
"""
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.
"""
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"
)