Root documentation: - README.md: add Datums description, update addon load order and SDK references, fix project structure tree, update issue reporting guidance - CONTRIBUTING.md: update submodule table (remove ztools, add gears/datums/solver), fix QSS guidance (single canonical source, not three copies) - docs/ARCHITECTURE.md: update bootstrap flow (5 addons, split deferred timers between Create core and Silo addon), update load order and source layout - docs/COMPONENTS.md: add Datums and Solver sections, update Gears description, fix Silo origin registration reference - docs/KNOWN_ISSUES.md: create missing file referenced by CLAUDE.md and CONTRIBUTING.md - docs/INTEGRATION_PLAN.md: update layer 5 diagram, fix load order references, update Phase 6 install rules, fix Layer 2/3/5 descriptions - docs/OVERVIEW.md: add datums submodule entry - docs/UPSTREAM.md: update Phase 1 directory table and Phase 4 submodule list mdBook documentation (docs/src/): - SUMMARY.md: replace dead architecture/ links with existing reference pages, remove deleted silo-server files, add new silo-server pages - introduction.md: rewrite — replace ztools with current addons (Silo, Gears, Datums, KCSDK), update version to v0.1.5/FreeCAD 1.2.0 - guide/getting-started.md: update first-run addon list - guide/installation.md: update verification console output - guide/workbenches.md: rewrite — replace ztools with Gears, Datums, Solver - guide/building.md: update submodule table, fix error message guidance - development/contributing.md: fix scope example and issue reporting - development/repo-structure.md: rewrite — add SDK, datums, gears, solver, reference/ folder; update submodule and key files tables - development/writing-an-addon.md: fix priority range table - reference/create-module-bootstrap.md: rewrite — reflect addon_loader system, split deferred timers between Create core and Silo addon - reference/datum-creator.md: update from ZTools to datums addon paths and naming - reference/glossary.md: add KCSDK entry, update FreeCAD version, remove ztools entry, update repository URLs table
7.7 KiB
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 <kindred> extension block. The <workbench> tag is required for InitGui.py to be loaded, even if your addon doesn't register a workbench.
<?xml version="1.0" encoding="UTF-8"?>
<package format="1">
<name>my-addon</name>
<description>My custom addon for Kindred Create.</description>
<version>0.1.0</version>
<maintainer email="you@example.com">Your Name</maintainer>
<license>LGPL-2.1-or-later</license>
<!-- Required for InitGui.py loading -->
<workbench>
<classname>MyAddonWorkbench</classname>
</workbench>
<kindred>
<min_create_version>0.1.5</min_create_version>
<load_priority>70</load_priority>
<pure_python>true</pure_python>
<dependencies>
<dependency>sdk</dependency>
</dependencies>
</kindred>
</package>
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 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.
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.
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 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.
# 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.
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.
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)
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:
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.
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:
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_sdkwrappers instead ofFreeCADGui.*internals. The SDK handles fallback and error logging. - Defer initialization with
QTimer.singleShot()to avoid startup timing issues. - Declare
<dependency>sdk</dependency>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.