Files
create/mods/sdk/kindred_sdk/dock.py
forbes 64644eb623 feat(sdk): add panel provider and theme engine to kcsdk (#352, #353)
- IPanelProvider: abstract interface for dock panels with PySide widget bridging
- PyIPanelProvider/PyProviderHolder: pybind11 trampoline + GIL-safe holder
- WidgetBridge: PySide QWidget → C++ QWidget* conversion via Shiboken
- SDKRegistry: panel registration, creation, and lifecycle management
- ThemeEngine: C++ singleton with minimal YAML parser, palette cache,
  getColor/allTokens/formatQss matching Python Palette API
- kcsdk bindings: DockArea, PanelPersistence enums, panel functions,
  theme_color, theme_tokens, format_qss, load_palette
- dock.py: kcsdk delegation with FreeCADGui fallback
- theme.py: kcsdk delegation with Python YAML fallback
2026-03-01 14:42:00 -06:00

145 lines
4.5 KiB
Python

"""Dock panel registration helper.
Routes through the ``kcsdk`` C++ module (IPanelProvider / DockWindowManager)
when available, falling back to direct PySide QDockWidget creation for
backwards compatibility.
"""
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 _get_dock_area(area_str):
"""Convert area string to kcsdk.DockArea enum value."""
global _DOCK_AREA_MAP
if _DOCK_AREA_MAP is None:
_DOCK_AREA_MAP = {
"left": _kcsdk.DockArea.Left,
"right": _kcsdk.DockArea.Right,
"top": _kcsdk.DockArea.Top,
"bottom": _kcsdk.DockArea.Bottom,
}
return _DOCK_AREA_MAP.get(area_str)
def register_dock_panel(object_name, title, widget_factory, area="right", delay_ms=0):
"""Register a dock panel, optionally deferred.
Parameters
----------
object_name : str
Qt object name for duplicate prevention.
title : str
Dock widget title bar text.
widget_factory : callable
Zero-argument callable returning a ``QWidget``. Called only
when the panel is actually created (after *delay_ms*).
area : str, optional
Dock area: ``"left"``, ``"right"``, ``"top"``, or ``"bottom"``.
Default ``"right"``.
delay_ms : int, optional
Milliseconds to wait before creating the panel. Default 0
(immediate, but still posted to the event loop).
"""
if not isinstance(object_name, str):
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_MAP:
raise ValueError(f"area must be one of {list(_AREA_MAP)}, got {area!r}")
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):
def id(self):
return object_name
def title(self):
return title
def create_widget(self):
return widget_factory()
def preferred_area(self):
return dock_area
try:
_kcsdk.register_panel(_AnonymousProvider())
def _create():
try:
_kcsdk.create_panel(object_name)
except Exception as e:
FreeCAD.Console.PrintLog(
f"kindred_sdk: Panel '{object_name}' creation failed: {e}\n"
)
from PySide.QtCore import QTimer
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"
)