パネルアセンブリチュートリアル
この応用チュートリアルでは、Option.table() 型を使ってセル配置を編集可能なグリッドとしてユーザーに提示し、それをパネルに組み立てる実用的なツールを構築します。
LinkCAD に同梱されている Panel Assembly ツール(plugins/python/tools/panelize.py)と対応しているため、本ページと合わせて実際のファイルを読むこともできます。
目的
セル参照 を配置することでセルをパネルに並べるツールを作ります。ジオメトリはコピーもフラット化もされないため、パネルは編集可能なまま、どの形式にもエクスポートできます。
完全なプラグイン
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)" ), }テーブルオプションの仕組み
Option.table() はツールダイアログに編集可能なグリッドを作ります。
- 列 は
TableColumnオブジェクトで定義します - 行 はユーザーが追加・削除します
- 値は列キーから値へのマッピングである
list[dict]——ダイアログが保存した場合はそのリストの JSON 文字列形式——です - 列の型には
string、integer、real、choice、cell_choiceがあります
TableColumn のプロパティ
| プロパティ | 説明 |
|---|---|
key | この列の値に対する辞書キー |
label | 列見出しのテキスト |
col_type | string、integer、real、choice、cell_choice |
default | 新しい行の既定値 |
choices | choice 列の選択肢 |
decimals | real 列の小数桁数 |
min_value | 数値列の最小値 |
max_value | 数値列の最大値 |
cell_choice 列型
cell_choice 列は、現在の図面のすべてのセル名を候補とするドロップダウンを表示します。ユーザーにセルを選ばせる場合はこれが推奨されます。
主要なパターン
セル参照を配置する
参照は、それを保持することになるセルが作成します。
assembly_cell.add_ref(ref_cell, transformation)add_ref() は 1 回の呼び出しで配列も作成できます。位置ごとに参照を作るより効率的です。
assembly_cell.add_ref( ref_cell, transformation, columns=8, rows=4, column_spacing=10_000, row_spacing=8_000,)使いものにならない値を返す代わりに ValueError を送出します。循環階層(セルを自身の子孫の中に配置すること)のときと、対象セルが存在しないときです。それでもこのツールは図面に手を加える前に自己参照を自分で検査しています。そうすればユーザーは、トランザクションの途中で出る例外ではなく、出力セル名を含むメッセージを受け取れます。
データベーストランザクションを使う
図面の変更は必ずトランザクションで包んでください。ブロックが正常に終了するとコミットされ、例外が抜けるとロールバックされます。つまり途中で失敗しても中途半端な状態は残りません。
from linkcad.v1.db import Transaction
with Transaction(drawing, "Panel Assembly"): ...説明文字列は取り消しエントリとして「Edit」メニューに表示されます。
単位変換
データベースは整数を格納しますが、その 1 単位がどれだけの大きさかは図面ごとに異なります。drawing.units は 1 メートルが何データベース単位かを示すもので、物理的な寸法を座標に変換するために必要な値です。
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_meterここで値を推測すると、ジオメトリがどこに配置されるかが決まってしまいます。図面が単位を宣言していない場合は、推測せずに処理を拒否してください。メートル換算係数は独自の表を書くのではなく linkcad.plugin.UNIT_IN_METERS から取ってください——使用できる名前は nm、um、mil、mm、cm、inch、m です。
コンソールから実行する
ツールのオプションは通常のインスタンス属性なので、ダイアログを開かずにスクリプトエディターから直接ツールを駆動できます。
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"])