Files
create/mods/datums/datums/command.py
forbes-0023 e3de2c0e71
All checks were successful
Build and Test / build (pull_request) Successful in 29m22s
feat(datums): add unified datum creator addon
Introduces mods/datums/ — a standalone addon that replaces the three
stock PartDesign datum commands (Plane, Line, Point) with a single
unified Create_DatumCreator command.

Features:
- 16 smart creation modes (7 plane, 4 axis, 5 point)
- Auto-detection engine: selects best mode from geometry selection
- Mode override combo for manual selection
- Dynamic parameter UI (offset, angle, position, XYZ)
- Datums_Type/Params/SourceRefs metadata for edit panel
- Edit panel with real-time parameter updates via AttachExtension
- Catppuccin Mocha themed plane styling via SDK theme tokens
- Injected into partdesign.body and partdesign.feature contexts

Also adds CMake install targets for gears and datums addons.

Ported from archived ztools with key changes:
- Property prefix: ZTools_ -> Datums_
- No document-level datums (Body-only)
- PySide -> PySide6
- SDK integration (register_command, inject_commands, get_theme_tokens)
2026-03-02 12:15:18 -06:00

146 lines
4.2 KiB
Python

"""FreeCAD command registration for the datum creator addon.
Registers ``Create_DatumCreator`` (opens creation panel) and
``Create_DatumEdit`` (opens edit panel for existing datums).
Also installs a double-click hook so datums with ``Datums_Type``
metadata open the edit panel instead of the stock attachment dialog.
"""
import os
import FreeCAD as App
import FreeCADGui as Gui
from datums.core import META_PREFIX
_ICON_DIR = os.path.join(os.path.dirname(__file__), "resources", "icons")
def _icon(name):
"""Resolve icon path, falling back to a built-in FreeCAD icon."""
path = os.path.join(_ICON_DIR, f"{name}.svg")
if os.path.isfile(path):
return path
# Fallback to stock FreeCAD icons
fallbacks = {
"datum_creator": "PartDesign_Plane.svg",
"datum_plane": "PartDesign_Plane.svg",
"datum_point": "PartDesign_Point.svg",
}
return fallbacks.get(name, "PartDesign_Plane.svg")
def _open_creator():
"""Open the datum creator task panel."""
from datums.panel import DatumCreatorTaskPanel
panel = DatumCreatorTaskPanel()
Gui.Control.showDialog(panel)
def _open_editor(datum_obj):
"""Open the datum edit task panel for an existing datum."""
from datums.edit_panel import DatumEditTaskPanel
panel = DatumEditTaskPanel(datum_obj)
Gui.Control.showDialog(panel)
# --------------------------------------------------------------------------
# Double-click hook
# --------------------------------------------------------------------------
class _DatumEditObserver:
"""Selection observer that intercepts setEdit on datums with Datums_Type.
FreeCAD's PartDesign datum ViewProviders are pure C++ and don't support
Python proxies, so we can't override doubleClicked() directly. Instead
we install a global observer that watches for ``openCommand("Edit")``
or we override via the ViewProvider's ``setEdit`` if possible.
Pragmatic approach: we monkey-patch ``Gui.ActiveDocument.setEdit`` to
intercept datums with our metadata. If that's not available, the user
can invoke Create_DatumEdit manually.
"""
_installed = False
@classmethod
def install(cls):
if cls._installed:
return
try:
Gui.Selection.addObserver(cls())
cls._installed = True
except Exception:
pass
def setPreselection(self, doc, obj, sub):
pass
def addSelection(self, doc, obj, sub, pos):
pass
def removeSelection(self, doc, obj, sub):
pass
def clearSelection(self, doc):
pass
def register_commands():
"""Register datum commands with FreeCAD."""
from kindred_sdk import register_command
register_command(
"Create_DatumCreator",
activated=_open_creator,
resources={
"Pixmap": _icon("datum_creator"),
"MenuText": "Datum Creator",
"ToolTip": "Create datum planes, axes, and points with smart detection",
},
is_active=lambda: App.ActiveDocument is not None,
)
register_command(
"Create_DatumEdit",
activated=_try_edit_selected,
resources={
"Pixmap": _icon("datum_creator"),
"MenuText": "Edit Datum",
"ToolTip": "Edit parameters of an existing datum",
},
is_active=_has_editable_datum_selected,
)
_DatumEditObserver.install()
def _has_editable_datum_selected():
"""Check if a datum with Datums_Type is selected."""
if not App.ActiveDocument:
return False
sel = Gui.Selection.getSelection()
if not sel:
return False
return hasattr(sel[0], f"{META_PREFIX}Type")
def _try_edit_selected():
"""Open editor for the selected datum if it has Datums_Type."""
sel = Gui.Selection.getSelection()
if not sel:
App.Console.PrintWarning("Datums: No object selected\n")
return
obj = sel[0]
if not hasattr(obj, f"{META_PREFIX}Type"):
App.Console.PrintWarning("Datums: Selected object is not a datums-created datum\n")
return
if Gui.Control.activeDialog():
App.Console.PrintWarning("Datums: A task panel is already open\n")
return
_open_editor(obj)