# Writing an Addon This guide walks through creating a Kindred Create addon from scratch. Addons are Python packages in the `mods/` directory that extend Create with commands, panels, and UI modifications through the SDK. ## Addon structure A minimal addon has this layout: ``` mods/my-addon/ ├── package.xml # Manifest (required) ├── Init.py # Console-phase bootstrap ├── InitGui.py # GUI-phase bootstrap └── my_addon/ ├── __init__.py └── commands.py # Your commands ``` ## Step 1: Create the manifest Every addon needs a `package.xml` with a `` extension block. The `` tag is required for `InitGui.py` to be loaded, even if your addon doesn't register a workbench. ```xml my-addon My custom addon for Kindred Create. 0.1.0 Your Name LGPL-2.1-or-later MyAddonWorkbench 0.1.5 70 true sdk ``` ### Priority ranges | Range | Use | |-------|-----| | 0-9 | SDK and core infrastructure | | 10-49 | Foundation addons (solver, gears, datums) | | 50-99 | Standard addons (silo) | | 100+ | Optional/user addons | See [Package.xml Schema Extensions](./package-xml-schema.md) for the full schema. ## Step 2: Console bootstrap (Init.py) `Init.py` runs during FreeCAD's console initialization, before the GUI exists. Use it for non-GUI setup. ```python import FreeCAD FreeCAD.Console.PrintLog("my-addon: loaded (console)\n") ``` ## Step 3: GUI bootstrap (InitGui.py) `InitGui.py` runs when the GUI is ready. This is where you register commands, contexts, panels, and overlays. ```python import FreeCAD import FreeCADGui FreeCAD.Console.PrintLog("my-addon: loaded (GUI)\n") def _deferred_setup(): """Register commands and UI after the main window is ready.""" from my_addon import commands commands.register() from PySide.QtCore import QTimer QTimer.singleShot(2000, _deferred_setup) ``` Deferred setup via `QTimer.singleShot()` avoids timing issues during startup. See [Create Module Bootstrap](../reference/create-module-bootstrap.md) for the full timer cascade. ## Step 4: Register commands FreeCAD commands use `Gui.addCommand()`. This is a stable FreeCAD API and does not need SDK wrappers. ```python # my_addon/commands.py import FreeCAD import FreeCADGui class MyCommand: def GetResources(self): return { "MenuText": "My Command", "ToolTip": "Does something useful", } def Activated(self): FreeCAD.Console.PrintMessage("My command activated\n") def IsActive(self): return FreeCAD.ActiveDocument is not None def register(): FreeCADGui.addCommand("MyAddon_MyCommand", MyCommand()) ``` ## Step 5: Inject into editing contexts Use the SDK to add your commands to existing toolbar contexts, rather than creating a standalone workbench. ```python from kindred_sdk import inject_commands # Add your command to the PartDesign body context toolbar inject_commands("partdesign.body", "PartDesign", ["MyAddon_MyCommand"]) ``` Built-in contexts you can inject into: `sketcher.edit`, `assembly.edit`, `partdesign.feature`, `partdesign.body`, `assembly.idle`, `spreadsheet`, `empty_document`, `no_document`. ## Step 6: Register a custom context If your addon has its own editing mode, register a context to control which toolbars are visible. ```python from kindred_sdk import register_context def _is_my_object_in_edit(): import FreeCADGui doc = FreeCADGui.activeDocument() if doc and doc.getInEdit(): obj = doc.getInEdit().Object return obj.isDerivedFrom("App::FeaturePython") and hasattr(obj, "MyAddonType") return False register_context( "myaddon.edit", "Editing {name}", "#f9e2af", # Catppuccin yellow ["MyAddonToolbar", "StandardViews"], _is_my_object_in_edit, priority=55, ) ``` ## Step 7: Register a dock panel For panels that live in the dock area (like Silo's database panels), use the SDK panel registration. ### Simple approach (recommended for most addons) ```python from kindred_sdk import register_dock_panel def _create_my_panel(): from PySide import QtWidgets widget = QtWidgets.QTreeWidget() widget.setHeaderLabels(["Name", "Value"]) return widget register_dock_panel( "MyAddonPanel", # unique object name "My Addon", # title bar text _create_my_panel, area="right", delay_ms=3000, # create 3 seconds after startup ) ``` ### Advanced approach (IPanelProvider) For full control over panel behavior, implement the `IPanelProvider` interface directly: ```python import kcsdk class MyPanelProvider(kcsdk.IPanelProvider): def id(self): return "myaddon.inspector" def title(self): return "Inspector" def create_widget(self): from PySide import QtWidgets tree = QtWidgets.QTreeWidget() tree.setHeaderLabels(["Property", "Value"]) return tree def preferred_area(self): return kcsdk.DockArea.Left def context_affinity(self): return "myaddon.edit" # only visible in your custom context # Register and create kcsdk.register_panel(MyPanelProvider()) kcsdk.create_panel("myaddon.inspector") ``` ## Step 8: Use theme colors The SDK provides the Catppuccin Mocha palette for consistent theming. ```python from kindred_sdk import get_theme_tokens, load_palette # Quick lookup tokens = get_theme_tokens() blue = tokens["blue"] # "#89b4fa" error = tokens["error"] # mapped from semantic role # Full palette object palette = load_palette() palette.get("accent.primary") # semantic role lookup palette.get("mauve") # direct color lookup # Format QSS templates qss = palette.format_qss("background: {base}; color: {text};") ``` ## Complete example Putting it all together, here's a minimal addon that adds a command and a dock panel: ``` mods/my-addon/ ├── package.xml ├── Init.py ├── InitGui.py └── my_addon/ ├── __init__.py └── commands.py ``` **InitGui.py:** ```python import FreeCAD def _setup(): from my_addon.commands import register from kindred_sdk import inject_commands, register_dock_panel register() inject_commands("partdesign.body", "PartDesign", ["MyAddon_MyCommand"]) from PySide import QtWidgets register_dock_panel( "MyAddonPanel", "My Addon", lambda: QtWidgets.QLabel("Hello from my addon"), area="right", delay_ms=0, ) from PySide.QtCore import QTimer QTimer.singleShot(2500, _setup) ``` ## Key patterns - **Use `kindred_sdk` wrappers** instead of `FreeCADGui.*` internals. The SDK handles fallback and error logging. - **Defer initialization** with `QTimer.singleShot()` to avoid startup timing issues. - **Declare `sdk`** in your manifest to ensure the SDK loads before your addon. - **Inject commands into existing contexts** rather than creating standalone workbenches. This gives users a unified toolbar experience. - **Use theme tokens** from the palette for colors. Don't hardcode hex values. ## Related - [KCSDK Python API Reference](../reference/kcsdk-python.md) - [Package.xml Schema Extensions](./package-xml-schema.md) - [Create Module Bootstrap](../reference/create-module-bootstrap.md)