Panel Assembly Tutorial
This advanced tutorial demonstrates building a real-world tool that uses the Option.table() type to create an interactive grid for specifying panel layouts.
The Goal
Create a “Panelizer” tool that arranges multiple cells into a panel grid, with configurable spacing and repetition.
Complete Plugin
from linkcad.v1.plugin import tool, Tool, Option, TableColumnfrom linkcad.v1.db import Drawing, Cell, Transactionfrom linkcad.v1.geom import Point
@tool( name="Panelize", menu="Tools/Drawing", tooltip="Arrange cells into a panel layout", requires_drawing=True,)class Panelizer(Tool): panel_name = Option.string( "Panel cell name", default="PANEL", tooltip="Name for the generated panel cell", )
entries = Option.table( "Panel Layout", columns=[ TableColumn( key="cell_name", label="Cell", col_type="cell_choice", ), TableColumn( key="offset_x", label="Offset X (µm)", col_type="real", default=0.0, decimals=3, ), TableColumn( key="offset_y", label="Offset Y (µm)", col_type="real", default=0.0, decimals=3, ), TableColumn( key="repeat_x", label="Repeat X", col_type="integer", default=1, min_value=1, max_value=1000, ), TableColumn( key="repeat_y", label="Repeat Y", col_type="integer", default=1, min_value=1, max_value=1000, ), TableColumn( key="pitch_x", label="Pitch X (µm)", col_type="real", default=1000.0, decimals=3, ), TableColumn( key="pitch_y", label="Pitch Y (µm)", col_type="real", default=1000.0, decimals=3, ), ], )
flatten_result = Option.boolean( "Flatten result", default=False, tooltip="Flatten the panel cell after assembly", )
def run(self, drawing) -> None: dwg = Drawing.current()
with Transaction(dwg) as txn: panel = Cell.create(dwg, self.panel_name)
for row in self.entries: cell_name = row["cell_name"] if not cell_name: continue
cell = dwg.find_cell(cell_name) if cell is None: print(f"Warning: cell '{cell_name}' not found, skipping") continue
ox = int(row["offset_x"] * 1000) # µm to nm oy = int(row["offset_y"] * 1000) rx = int(row.get("repeat_x", 1)) ry = int(row.get("repeat_y", 1)) px = int(row["pitch_x"] * 1000) py = int(row["pitch_y"] * 1000)
for ix in range(rx): for iy in range(ry): x = ox + ix * px y = oy + iy * py panel.add_ref(cell, Point(x, y))
# auto-commits on successful exit
print(f"Created panel '{self.panel_name}' with " f"{len(self.entries)} cell group(s)")How the Table Option Works
Option.table() creates an editable grid in the tool dialog:
- Columns are defined by
TableColumnobjects - Rows are added/removed by the user via buttons
- The value is a
list[dict]where each dict maps column keys to values - Column types include
string,integer,real,choice, andcell_choice
TableColumn Properties
| Property | Description |
|---|---|
key | Dict key for this column’s value |
label | Column header text |
col_type | string, integer, real, choice, cell_choice |
default | Default value for new rows |
choices | Options for choice columns |
decimals | Decimal places for real columns |
min_value | Minimum for numeric columns |
max_value | Maximum for numeric columns |
The cell_choice Column Type
A cell_choice column renders a dropdown populated with all cell names from the current drawing. This is the recommended way to let users select cells.
Key Patterns
Accessing Table Rows
for row in self.entries: cell_name = row["cell_name"] offset_x = row["offset_x"]Using Database Transactions
Always wrap drawing modifications in a transaction:
with Transaction(dwg) as txn: # ... modify drawing ... # auto-commits on successful exit; auto-rolls-back on exceptionUnit Conversion
The drawing database uses nanometers internally. Convert from user-facing units:
x_nm = int(x_um * 1000) # µm → nm