Files
create/docs/src/development/writing-an-addon.md
forbes 7f02fd182e
All checks were successful
Build and Test / build (pull_request) Successful in 24m46s
docs: comprehensive documentation refresh — remove stale references, add missing content
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
2026-03-03 13:52:53 -06:00

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.

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_sdk wrappers instead of FreeCADGui.* 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.