Tutorial zur Panel-Zusammenstellung
Dieses fortgeschrittene Tutorial baut ein praxisnahes Tool, das über den Typ Option.table() ein editierbares Raster für Zellenplatzierungen anbietet und daraus ein Panel zusammensetzt.
Es entspricht dem mit LinkCAD ausgelieferten Tool Panel Assembly (plugins/python/tools/panelize.py), sodass Sie die Originaldatei parallel zu dieser Seite lesen können.
Das Ziel
Ein Tool, das Zellen zu einem Panel anordnet, indem es Zellenreferenzen platziert — es wird keine Geometrie kopiert oder reduziert, das Panel bleibt also editierbar und lässt sich in jedes Format exportieren.
Vollständiges Plugin
import jsonimport loggingfrom typing import Any, Dict, List
from linkcad.v1.plugin import UNIT_IN_METERS, Option, TableColumn, Tool, tool
logger = logging.getLogger(__name__)
# Placement coordinates are entered in whichever unit the user picked, while the# database stores integers whose size comes from `drawing.units` (database units# per metre), so the two have to be reconciled before anything is placed. The# conversion table is linkcad.plugin's: a second table that can silently# disagree is how a thousand-fold placement error once got into this file.
@tool( name="Panel Assembly", menu="Tools/Drawing", tooltip="Build a panel by placing cell references at specified positions", requires_drawing=True,)class PanelizeTool(Tool): placements = Option.table( "Placements", columns=[ TableColumn(key="cell_name", label="Cell", col_type="cell_choice"), TableColumn(key="x", label="X", col_type="real", default=0.0, decimals=3), TableColumn(key="y", label="Y", col_type="real", default=0.0, decimals=3), TableColumn( key="rotation", label="Rotation (°)", col_type="real", default=0.0, decimals=1, min_value=-360.0, max_value=360.0, ), ], default=[], tooltip="Cell placement list: select a cell and enter coordinates", )
units = Option.choice( "Placement units", choices=["nm", "um", "mil", "mm", "cm", "inch", "m"], default="mm", tooltip="Units the X and Y columns are given in", )
output_cell_name = Option.string( "Output Cell Name", default="PANEL_ASSEMBLY", tooltip="Name of the new panel assembly cell", )
set_as_main = Option.boolean( "Set as Main Cell", default=True, tooltip="Make the panel assembly cell the main (top) cell", )
def run(self, drawing: Any) -> Dict[str, Any]: from linkcad.v1.db import Transaction from linkcad.v1.geom import Transformation
# The dialog stores table rows as a JSON string; a script may pass a list. raw = self.placements if isinstance(raw, str): rows: List[Dict[str, Any]] = json.loads(raw) if raw else [] elif isinstance(raw, list): rows = raw else: rows = []
if not rows: raise ValueError( "No placements defined. Add at least one row to the placement table." )
# Validate every referenced cell before changing anything. cell_names_needed = {r["cell_name"] for r in rows if r.get("cell_name")} missing = sorted(n for n in cell_names_needed if drawing.cell(n) is None) if missing: raise ValueError( "The following cells are not in the drawing: " + ", ".join(missing) + ". Import the required designs first." )
output_name = self.output_cell_name or "PANEL_ASSEMBLY"
# A cell placed inside itself corrupts the cell tree, so refuse it up front. if output_name in cell_names_needed: raise ValueError( f"Output cell '{output_name}' cannot also be used as a placement " f"cell. This would create a circular cell reference." )
db_units_per_meter = float(drawing.units) if db_units_per_meter <= 0.0: raise ValueError( "The drawing does not declare its database units, so placement " "coordinates cannot be converted." ) to_db_units = UNIT_IN_METERS[self.units] * db_units_per_meter
placed_count = 0
with Transaction(drawing, "Panel Assembly"): assembly_cell = drawing.add_cell(output_name)
# If the cell already existed, clear it so re-running the tool does # not duplicate every placement. for obj in assembly_cell.cell_objects: obj.destroy()
for row in rows: cell_name = row.get("cell_name", "") if not cell_name: continue
x = float(row.get("x", 0.0)) y = float(row.get("y", 0.0)) rotation = float(row.get("rotation", 0.0))
ref_cell = drawing.cell(cell_name)
# Rotate before translating. Transformation.rotate() also rotates # whatever offset is already set, so the other order would swing # each cell away from its intended spot. xform = Transformation() if rotation != 0.0: xform.rotate(rotation) xform.translate(x * to_db_units, y * to_db_units)
assembly_cell.add_ref(ref_cell, transformation=xform) placed_count += 1
if self.set_as_main: drawing.main_cell = assembly_cell
return { "title": "Panel Assembly", "cell_name": output_name, "placements": placed_count, "unique_cells": len(cell_names_needed), "summary": ( f"Created '{output_name}' with {placed_count} cell references " f"from {len(cell_names_needed)} unique cell(s)" ), }Wie die Tabellenoption funktioniert
Option.table() erzeugt ein editierbares Raster im Tool-Dialog:
- Spalten werden über
TableColumn-Objekte definiert - Zeilen fügt der Benutzer hinzu oder entfernt sie
- Der Wert ist eine
list[dict], die Spaltenschlüssel auf Werte abbildet — oder die JSON-Zeichenkettenform dieser Liste, wenn der Dialog sie gespeichert hat - Als Spaltentypen stehen
string,integer,real,choiceundcell_choicezur Verfügung
TableColumn-Eigenschaften
| Eigenschaft | Beschreibung |
|---|---|
key | Dictionary-Schlüssel für den Wert dieser Spalte |
label | Spaltenüberschrift |
col_type | string, integer, real, choice, cell_choice |
default | Standardwert für neue Zeilen |
choices | Auswahlmöglichkeiten für choice-Spalten |
decimals | Nachkommastellen für real-Spalten |
min_value | Minimum für numerische Spalten |
max_value | Maximum für numerische Spalten |
Der Spaltentyp cell_choice
Eine cell_choice-Spalte zeigt eine Auswahlliste mit allen Zellennamen der aktuellen Zeichnung. Das ist der empfohlene Weg, Benutzer Zellen auswählen zu lassen.
Wichtige Muster
Eine Zellenreferenz platzieren
Eine Referenz wird von der Zelle erzeugt, die sie enthalten wird:
assembly_cell.add_ref(ref_cell, transformation)add_ref() erzeugt auch Anordnungen in einem Aufruf, was günstiger ist als eine Referenz pro Position:
assembly_cell.add_ref( ref_cell, transformation, columns=8, rows=4, column_spacing=10_000, row_spacing=8_000,)Es löst ValueError aus, statt etwas Unbrauchbares zurückzugeben — bei einer zirkulären Hierarchie (eine Zelle in einem ihrer eigenen Nachfahren zu platzieren) und bei einer fehlenden Zielzelle. Das Tool prüft die Selbstreferenz trotzdem selbst, bevor es die Zeichnung anfasst, damit die Benutzerin eine Meldung mit dem Namen der Ausgabezelle bekommt statt einer Ausnahme mitten in der Transaktion.
Datenbanktransaktionen verwenden
Kapseln Sie Änderungen an der Zeichnung immer in eine Transaktion. Sie wird bestätigt, wenn der Block normal endet, und zurückgerollt, wenn eine Ausnahme entweicht — ein Fehlschlag auf halbem Weg hinterlässt also nichts Halbfertiges:
from linkcad.v1.db import Transaction
with Transaction(drawing, "Panel Assembly"): ...Die Beschreibungszeichenkette erscheint im Menü „Edit“ als Rückgängig-Eintrag.
Einheitenumrechnung
Die Datenbank speichert ganze Zahlen, und wie groß eine solche Zahl ist, hängt von der Zeichnung ab. drawing.units gibt an, wie viele Datenbankeinheiten einen Meter ergeben — genau das wandelt eine physikalische Größe in eine Koordinate um:
from linkcad.v1.plugin import UNIT_IN_METERS
db_units_per_meter = float(drawing.units)if db_units_per_meter <= 0.0: raise ValueError("This drawing does not declare its database units")
x_db = x_mm * UNIT_IN_METERS["mm"] * db_units_per_meterEine Schätzung würde hier darüber entscheiden, wo Geometrie landet. Verweigern Sie die Arbeit lieber, statt zu raten, wenn die Zeichnung ihre Einheiten nicht deklariert. Nehmen Sie den Faktor Meter-pro-Einheit aus linkcad.plugin.UNIT_IN_METERS, statt eine eigene Tabelle zu schreiben — zulässig sind nm, um, mil, mm, cm, inch und m.
Aus der Konsole ausführen
Tool-Optionen sind gewöhnliche Instanzattribute, ein Tool lässt sich also direkt aus dem Skripteditor steuern, ohne seinen Dialog zu öffnen:
from tools.panelize import PanelizeTool
t = PanelizeTool()t.placements = [{"cell_name": "UNIT", "x": 5.0, "y": 2.5, "rotation": 0.0}]t.units = "mm"t.output_cell_name = "PANEL"print(t.run(drawing)["summary"])