docs: add example addon template (#395)
All checks were successful
Build and Test / build (pull_request) Successful in 30m3s
All checks were successful
Build and Test / build (pull_request) Successful in 30m3s
- Create docs/examples/example-addon/ with a complete, copy-paste-ready addon skeleton demonstrating command registration, context injection, dock panels, lifecycle hooks, and event bus subscription - Add Examples section to docs/src/SUMMARY.md - Add quick-start cross-reference in writing-an-addon.md
This commit is contained in:
12
docs/examples/example-addon/Init.py
Normal file
12
docs/examples/example-addon/Init.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Example addon — console initialization.
|
||||
|
||||
This file runs in both console and GUI mode. Use it for non-GUI setup
|
||||
such as registering custom property types or document observers that
|
||||
should work without a display.
|
||||
|
||||
Most addons only need a log line here. Heavy setup belongs in InitGui.py.
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintLog("example-addon: console init\n")
|
||||
93
docs/examples/example-addon/InitGui.py
Normal file
93
docs/examples/example-addon/InitGui.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Example addon — GUI initialization.
|
||||
|
||||
Demonstrates the standard addon bootstrap pattern using the Kindred SDK.
|
||||
Each registration step is wrapped in try/except and deferred with
|
||||
QTimer.singleShot to avoid blocking the startup sequence.
|
||||
"""
|
||||
|
||||
|
||||
def _register_commands():
|
||||
"""Register commands and inject into existing contexts."""
|
||||
try:
|
||||
from example_addon.commands import register_commands
|
||||
|
||||
register_commands()
|
||||
except Exception as e:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(f"example-addon: command registration failed: {e}\n")
|
||||
|
||||
# Inject our command into an existing context's toolbar so it appears
|
||||
# automatically when the user enters that context.
|
||||
try:
|
||||
from kindred_sdk import inject_commands
|
||||
|
||||
inject_commands("partdesign.body", "Part Design", ["ExampleAddon_Hello"])
|
||||
except Exception as e:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(f"example-addon: context injection failed: {e}\n")
|
||||
|
||||
|
||||
def _register_panel():
|
||||
"""Register a dock panel with deferred creation."""
|
||||
try:
|
||||
from kindred_sdk import register_dock_panel
|
||||
from example_addon.panel import create_panel
|
||||
|
||||
register_dock_panel(
|
||||
"ExampleAddonPanel",
|
||||
"Example Panel",
|
||||
create_panel,
|
||||
area="right",
|
||||
delay_ms=2000,
|
||||
)
|
||||
except Exception as e:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(f"example-addon: panel registration failed: {e}\n")
|
||||
|
||||
|
||||
def _register_lifecycle():
|
||||
"""Subscribe to context lifecycle events.
|
||||
|
||||
Use ``on_context_enter`` / ``on_context_exit`` to react when the user
|
||||
enters or leaves a specific editing context. Pass ``"*"`` to match
|
||||
all contexts.
|
||||
"""
|
||||
try:
|
||||
from kindred_sdk import on_context_enter, on_context_exit
|
||||
|
||||
on_context_enter("*", lambda ctx: print(f"[example-addon] entered: {ctx['id']}"))
|
||||
on_context_exit("*", lambda ctx: print(f"[example-addon] exited: {ctx['id']}"))
|
||||
except Exception as e:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(f"example-addon: lifecycle hooks failed: {e}\n")
|
||||
|
||||
|
||||
def _register_events():
|
||||
"""Subscribe to event bus events.
|
||||
|
||||
The event bus lets addons communicate without direct imports.
|
||||
Use ``kindred_sdk.on(event, handler)`` to listen and
|
||||
``kindred_sdk.emit(event, data)`` to publish.
|
||||
"""
|
||||
try:
|
||||
from kindred_sdk import on
|
||||
|
||||
on("document.saved", lambda data: print(f"[example-addon] document saved: {data}"))
|
||||
except Exception as e:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(f"example-addon: event subscription failed: {e}\n")
|
||||
|
||||
|
||||
# Deferred initialization — stagger delays to avoid blocking startup.
|
||||
# Lower delays run first; keep each step fast.
|
||||
from PySide6.QtCore import QTimer
|
||||
|
||||
QTimer.singleShot(500, _register_commands)
|
||||
QTimer.singleShot(600, _register_panel)
|
||||
QTimer.singleShot(700, _register_lifecycle)
|
||||
QTimer.singleShot(800, _register_events)
|
||||
75
docs/examples/example-addon/README.md
Normal file
75
docs/examples/example-addon/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Example Addon Template
|
||||
|
||||
A minimal but complete Kindred Create addon that demonstrates all major SDK integration points. Copy this directory into `mods/`, rename it, and start building.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
example-addon/
|
||||
├── package.xml Addon manifest with <kindred> extensions
|
||||
├── Init.py Console-mode bootstrap (runs in all modes)
|
||||
├── InitGui.py GUI bootstrap — deferred registration
|
||||
└── example_addon/
|
||||
├── __init__.py Python package marker
|
||||
├── commands.py FreeCAD command via kindred_sdk.register_command()
|
||||
└── panel.py Dock panel widget factory
|
||||
```
|
||||
|
||||
## What This Template Demonstrates
|
||||
|
||||
| Feature | File | SDK function |
|
||||
|---------|------|-------------|
|
||||
| Addon manifest with version bounds and dependencies | `package.xml` | — |
|
||||
| Console bootstrap | `Init.py` | — |
|
||||
| Deferred GUI initialization with `QTimer.singleShot` | `InitGui.py` | — |
|
||||
| Command registration | `commands.py` | `kindred_sdk.register_command()` |
|
||||
| Injecting commands into existing contexts | `InitGui.py` | `kindred_sdk.inject_commands()` |
|
||||
| Dock panel registration | `InitGui.py`, `panel.py` | `kindred_sdk.register_dock_panel()` |
|
||||
| Context lifecycle hooks | `InitGui.py` | `kindred_sdk.on_context_enter()` |
|
||||
| Event bus subscription | `InitGui.py` | `kindred_sdk.on()` |
|
||||
|
||||
## Installation (for testing)
|
||||
|
||||
1. Copy or symlink this directory into the `mods/` folder at the repository root:
|
||||
|
||||
```bash
|
||||
cp -r docs/examples/example-addon mods/example-addon
|
||||
```
|
||||
|
||||
2. Launch Kindred Create:
|
||||
|
||||
```bash
|
||||
pixi run freecad
|
||||
```
|
||||
|
||||
3. Open the Python console (View > Panels > Python console) and verify:
|
||||
|
||||
```
|
||||
example-addon: console init
|
||||
```
|
||||
|
||||
4. The "Example Panel" dock widget should appear on the right after ~2 seconds.
|
||||
|
||||
5. To test the command, create a Part Design Body and look for "Hello World" in the Part Design toolbar.
|
||||
|
||||
## Customizing
|
||||
|
||||
1. **Rename** — change `example-addon` and `example_addon` to your addon's name in all files
|
||||
2. **Update `package.xml`** — set your name, description, repository URL, and load priority
|
||||
3. **Add commands** — create more functions in `commands.py` and register them
|
||||
4. **Add contexts** — use `kindred_sdk.register_context()` for custom editing modes
|
||||
5. **Add overlays** — use `kindred_sdk.register_overlay()` for conditional toolbars
|
||||
6. **Theme colors** — use `kindred_sdk.get_theme_tokens()` for Catppuccin Mocha palette access
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Missing `<workbench>` tag** — `InitGui.py` will not be loaded without it, even if you don't register a workbench
|
||||
- **Import at module level** — avoid importing `kindred_sdk` or `PySide6` at the top of `InitGui.py`; use deferred imports inside functions to avoid load-order issues
|
||||
- **Blocking startup** — keep each `QTimer.singleShot` callback fast; do heavy work in background threads
|
||||
- **Forgetting `<dependency>sdk</dependency>`** — your addon may load before the SDK if you omit this
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Writing an Addon](../../src/development/writing-an-addon.md) — full tutorial
|
||||
- [Package.xml Schema](../../src/development/package-xml-schema.md) — manifest reference
|
||||
- [KCSDK Python API](../../src/reference/kcsdk-python.md) — complete API reference
|
||||
1
docs/examples/example-addon/example_addon/__init__.py
Normal file
1
docs/examples/example-addon/example_addon/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Example addon for Kindred Create."""
|
||||
29
docs/examples/example-addon/example_addon/commands.py
Normal file
29
docs/examples/example-addon/example_addon/commands.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Example addon commands.
|
||||
|
||||
Each command is registered via ``kindred_sdk.register_command()`` which
|
||||
wraps FreeCAD's ``Gui.addCommand()`` with input validation.
|
||||
"""
|
||||
|
||||
from kindred_sdk import register_command
|
||||
|
||||
|
||||
def register_commands():
|
||||
"""Register all commands for this addon."""
|
||||
register_command(
|
||||
name="ExampleAddon_Hello",
|
||||
activated=_on_hello,
|
||||
resources={
|
||||
"MenuText": "Hello World",
|
||||
"ToolTip": "Show a greeting in the console",
|
||||
# "Pixmap": "path/to/icon.svg", # optional icon
|
||||
# "Accel": "Ctrl+Shift+H", # optional shortcut
|
||||
},
|
||||
is_active=lambda: True,
|
||||
)
|
||||
|
||||
|
||||
def _on_hello():
|
||||
"""Command handler — prints a greeting to the FreeCAD console."""
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintMessage("Hello from example-addon!\n")
|
||||
21
docs/examples/example-addon/example_addon/panel.py
Normal file
21
docs/examples/example-addon/example_addon/panel.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Example addon dock panel.
|
||||
|
||||
The factory function is passed to ``kindred_sdk.register_dock_panel()``
|
||||
and called once after the configured delay to create the widget.
|
||||
"""
|
||||
|
||||
|
||||
def create_panel():
|
||||
"""Create the example dock panel widget.
|
||||
|
||||
Must return a QWidget. The widget is embedded in a QDockWidget and
|
||||
managed by FreeCAD's DockWindowManager.
|
||||
"""
|
||||
from PySide6 import QtWidgets
|
||||
|
||||
widget = QtWidgets.QWidget()
|
||||
layout = QtWidgets.QVBoxLayout(widget)
|
||||
layout.addWidget(QtWidgets.QLabel("Example Addon Panel"))
|
||||
layout.addWidget(QtWidgets.QLabel("Replace this with your content."))
|
||||
layout.addStretch()
|
||||
return widget
|
||||
27
docs/examples/example-addon/package.xml
Normal file
27
docs/examples/example-addon/package.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||
<name>example-addon</name>
|
||||
<description>Example addon template for Kindred Create</description>
|
||||
<version>0.1.0</version>
|
||||
<maintainer email="you@example.com">Your Name</maintainer>
|
||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||
<url type="repository">https://example.com/your-addon</url>
|
||||
|
||||
<content>
|
||||
<!-- A <workbench> tag is required for InitGui.py to be loaded,
|
||||
even if the addon does not register an actual workbench. -->
|
||||
<workbench>
|
||||
<classname>ExampleAddonProvider</classname>
|
||||
<subdirectory>example_addon</subdirectory>
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
<!-- Kindred-specific extensions — ignored by stock FreeCAD. -->
|
||||
<kindred>
|
||||
<min_create_version>0.1.0</min_create_version>
|
||||
<load_priority>80</load_priority>
|
||||
<dependencies>
|
||||
<dependency>sdk</dependency>
|
||||
</dependencies>
|
||||
</kindred>
|
||||
</package>
|
||||
Reference in New Issue
Block a user