Skip to content

Writing a Tool Plugin

This tutorial shows how to create a Python tool that appears in LinkCAD’s menu system with an auto-generated options dialog.

The Tool Framework

A tool plugin consists of:

  1. A class decorated with @tool() that extends Tool
  2. Option class attributes that define the UI dialog
  3. A run() method that implements the logic and returns a result dictionary

The drawing handed to run() is a linkcad.v1.db.Drawing — the live database, owned by the application. Read it through the database API: drawing.cells, drawing.layers, cell.shapes, and so on.

Example: Layer Statistics

Create a file layer_stats.py in your plugins directory:

from typing import Any, Dict
from linkcad.v1.plugin import tool, Tool, Option
@tool(
name="Layer Statistics",
menu="Tools/Analysis",
tooltip="Show shape count per layer",
)
class LayerStats(Tool):
include_empty = Option.boolean(
"Include empty layers",
default=False,
tooltip="Show layers with zero shapes",
)
def run(self, drawing) -> Dict[str, Any]:
counts = {layer.name: 0 for layer in drawing.layers}
# One pass over the drawing, sorting each shape into its layer's tally.
# Walking the drawing once per layer would re-read every shape as many
# times as there are layers.
for cell in drawing.cells:
for shape in cell.shapes:
if shape.layer is not None:
counts[shape.layer.name] = counts.get(shape.layer.name, 0) + 1
if not self.include_empty:
counts = {name: n for name, n in counts.items() if n}
return {
"layers": counts,
"summary": f"{len(counts)} layers, {sum(counts.values())} shapes",
}
def format_result(self, result: Dict[str, Any]) -> str:
lines = [f"{name}: {n} shapes" for name, n in result["layers"].items()]
lines += ["", result["summary"]]
return "\n".join(lines)

How It Works

  1. @tool() registers the class as a LinkCAD tool
  2. name appears in the menu, menu sets the menu path
  3. Option.boolean(...) creates a checkbox in the auto-generated dialog
  4. run() is called when the user clicks OK in the dialog, and returns a result dict
  5. format_result() turns that dict into the text LinkCAD shows. Override it for anything nicer than the default

Modifying the Drawing

The framework holds a read lock while run() executes. To write, open a transaction — it commits on success, rolls back on an exception, and becomes one entry in the undo history:

from linkcad.v1.db import Transaction
def run(self, drawing):
with Transaction(drawing, "Add Frame"):
frame = drawing.add_cell("FRAME")
layer = drawing.add_layer("OUTLINE")
b = drawing.main_cell.bounds
frame.add_polyline(
layer,
[(b.min_x, b.min_y), (b.max_x, b.min_y),
(b.max_x, b.max_y), (b.min_x, b.max_y)],
closed=True,
)
return {"summary": "Added FRAME"}

Option Types

FactoryUI ControlExample
Option.integer()Spin boxOption.integer("Count", default=1, min=0, max=100)
Option.real()Double spin boxOption.real("Scale", default=1.0, decimals=4)
Option.boolean()CheckboxOption.boolean("Enable", default=True)
Option.string()Text fieldOption.string("Name", default="output")
Option.choice()DropdownOption.choice("Mode", choices=["Fast", "Precise"])
Option.path()File pickerOption.path("Output", file_filter="*.csv")
Option.color()Color pickerOption.color("Fill", default="#FF0000")
Option.table()Editable gridSee Panel Assembly
Option.cell_choice()Cell dropdownOption.cell_choice("Target Cell")

Conditional Options

Use enabled_when to show/hide options dynamically:

class MyTool(Tool):
mode = Option.choice("Mode", choices=["Simple", "Advanced"])
threshold = Option.real(
"Threshold",
default=0.5,
enabled_when=lambda self: self.mode == "Advanced",
)

The threshold field is only enabled when mode is “Advanced.”

Keyboard Shortcut

@tool(
name="My Tool",
menu="Tools/Custom",
shortcut="Ctrl+Shift+M",
)
class MyTool(Tool):
...

Next Steps