Skip to content

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, TableColumn
from linkcad.v1.db import Drawing, Cell, Transaction
from 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 TableColumn objects
  • 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, and cell_choice

TableColumn Properties

PropertyDescription
keyDict key for this column’s value
labelColumn header text
col_typestring, integer, real, choice, cell_choice
defaultDefault value for new rows
choicesOptions for choice columns
decimalsDecimal places for real columns
min_valueMinimum for numeric columns
max_valueMaximum 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 exception

Unit Conversion

The drawing database uses nanometers internally. Convert from user-facing units:

x_nm = int(x_um * 1000) # µm → nm