Skip to content

@tool() Decorator

Registers a class as a LinkCAD tool that appears in the application menu with an auto-generated options dialog.

Signature

@tool(
name: str,
menu: str,
tooltip: str = "",
shortcut: str = "",
icon: str = "",
requires_drawing: bool = True,
)
class MyTool(Tool):
...

Parameters

ParameterTypeDefaultDescription
namestrrequiredDisplay name in the menu
menustrrequiredMenu path (use / for submenus), e.g. "Tools/Analysis"
tooltipstr""Tooltip shown on hover
shortcutstr""Keyboard shortcut (e.g. "Ctrl+Shift+M")
iconstr""Icon filename, relative to res/
requires_drawingboolTrueWhether the tool is disabled when no drawing is loaded

Tool Base Class

All tool plugins must extend Tool and implement run():

class Tool:
def run(self, drawing) -> dict:
"""Override to implement the tool logic. Return a result dictionary."""
...
def format_result(self, result: dict) -> str:
"""Override to control how the result is displayed."""
...

The drawing argument is a linkcad.v1.db.Drawing — the live database, owned by the application, so never destroy it. If requires_drawing=False, it may be None.

run() returns a result dictionary. The default format_result() prints result["summary"] if present, so include one. Override format_result() for anything richer.

The framework holds a read lock while run() executes. Wrap modifications in a Transaction to make them undoable.

ToolInfo

The decorator creates a ToolInfo object attached to the class:

FieldDescription
nameTool display name
menuMenu path
tooltipTooltip text
shortcutKeyboard shortcut
iconIcon filename
requires_drawingWhether a drawing is required
optionsThe Option attributes collected from the class

The menu parameter defines where the tool appears in LinkCAD’s menu:

"Edit/Analyze" → Edit → Analyze → <tool name>
"Tools/Analysis" → Tools → Analysis → <tool name>
"Tools/Drawing/Custom" → Tools → Drawing → Custom → <tool name>

Complete Example

from typing import Any, Dict
from linkcad.v1.plugin import tool, Tool, Option
@tool(
name="Shape Counter",
menu="Tools/Analysis",
tooltip="Count shapes per layer",
shortcut="Ctrl+Shift+C",
requires_drawing=True,
)
class ShapeCounter(Tool):
closed_only = Option.boolean("Closed shapes only", default=False)
def run(self, drawing) -> Dict[str, Any]:
counts: Dict[str, int] = {}
for cell in drawing.cells:
for shape in cell.shapes:
if self.closed_only and not shape.closed:
continue
layer = shape.layer
if layer is not None:
counts[layer.name] = counts.get(layer.name, 0) + 1
return {
"layers": counts,
"summary": f"{sum(counts.values())} shapes on {len(counts)} layers",
}
def format_result(self, result: Dict[str, Any]) -> str:
lines = [f"{name}: {n}" for name, n in sorted(result["layers"].items())]
lines += ["", result["summary"]]
return "\n".join(lines)