zudo-panel-designer docs
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

ラウンドトリップ

エディタの ⬇ JSON ボタン(packages/app/src/editor/download.tsx)は、現在のドキュメントを 発注可能なパネル設定 JSON としてダウンロードします。そして今では、それに対応するインポート 経路もアプリに戻ってきています(ヘッダーの ⬆ JSON ボタン、ページ上のどこへでもドラッグ &ドロップ、あるいはコマンドパレットの Import JSON コマンド——詳しくは下の インポートを参照)。ここでの「ラウンドトリップ」とは、ダウンロードした JSON を parsePanelConfig() に通し直すと、画面上のドキュメントが再現されることを意味します。 この保証こそが、このファイルを注文成果物として、また将来のツール間の交換フォーマットとして 信頼できるものにしています。

ダウンロードのステップ

downloadPanelConfig(doc)serializePanelConfig(doc) を呼び、それを整形して出力し (JSON.stringify(config, null, 2))、zpd-panel-<hp>hp.json という名前でブラウザの ダウンロードを開始します。純粋なシリアライズのステップ——panelConfigJson(doc)——は、 DOM/Blob の仕組みから切り離されており、実際にダウンロードしなくても正確な JSON 文字列を ユニットテストできるようになっています。

インポート

3 つの入り口が、まったく同じコードパス(import.tsimportJsonFile())に集約されます。そのため、ファイルがどうやって届いたかにかかわらず、「ファイルをインポートする」という挙動は同一です。

  • ヘッダーの ⬆ JSON ボタン — ref の裏にある、常駐する非表示の <input type="file">

  • どこにでもドロップできるインポートcomponents/drop-import.tsx) — 正確なキャンバスのターゲットだけでなく、ページのどこにでも .json ファイル(あるいは画像——下記参照)をドロップできます。ファイルがウィンドウ上にドラッグされている間、ページ全体を覆うオーバーレイ("Drop image or panel JSON")が現れます。これは、カーソルがネストした要素をまたいでもオーバーレイがちらつかないよう、enter カウントを均衡させた dragenterdragleave のペアで追跡されます。ファイルのドラッグだけが捕捉され(dataTransfer.types.includes('Files'))、レイヤー名変更フィールドのようなアプリ内のフィールドへ選択したテキストをドラッグする操作は、完全にそのまま残されます。

  • コマンドパレットの Import JSON コマンド(chordless、パレット専用) — 一時的なネイティブファイルピッカーを開きます。

インポートのパイプラインは 厳格 です。このページの他の場所で使われている寛容な parsePanelConfig() とは異なり、生の JSON を core の tryParsePanelConfig() に通します。これはエンベロープのマーカー(app === 'zpd'、サポート範囲内の versionlayers 配列)を 先に チェックしてから、防御的パースで説明した、同じフィールドごとの防御的パースへ委譲します。この使い分けが存在するのは、インポート UX が「これはそもそも zpd のパネル設定ではない」ことと「これは本物の設定だが 1 つだけおかしなフィールドがある」ことを区別する必要があるためです——parsePanelConfig() 単独ではそれができません(設計上、決して失敗しないので。防御的パース → これが重要な理由を参照)。

拒否されたファイル(無効な JSON、app の不一致、version の欠落/範囲外、layers 配列がない、など)に対しては、現在のドキュメントは完全にそのまま残され、エラートーストがその理由を伝えます——"Could not import panel JSON" に、短い理由(例: "File is not valid JSON.")が添えられます。有効なファイルは、何かを変更する前に確認を求めます。

Replace current panel? This replaces the current panel with the imported one. This cannot be undone. [Cancel] [Replace]

確認すると、New panelが使うのと同じ replaceDoc() プリミティブを通じてドキュメント全体を置き換えます——undo/redo 履歴を破棄し、選択をクリアし、古い画像キャッシュエントリを退避させ——それから成功トースト("Panel imported")を表示します。

画像をドロップする場合

ドラッグ&ドロップは、パネル JSON ファイルに加えて画像/SVG ファイルも受け付けます。isImportableImageFile() は、あらゆる image/* MIME タイプ、.svg 拡張子、または MIME が空で JSON ではない匿名/拡張子なしファイルを受け付け、最後のケースは内容 sniffing に委ねます。ファイルは Add image… とクリップボードペーストも使う同じ routeImportFile() 分類器を通ります。ラスタ内容はデコードされ、(拡大せず縮小のみ)パネル幅の 80%/パネル高さの 50% に収まるようスケールされ、パネル左上からの固定オフセット(幅の 10%/高さの 15%)に配置されて、Copper 内の新しい選択済みデザイン用 image としてコミットされます。実体が SVG の場合はベクターインポートダイアログを開き、各ソース paint を Copper、Solder mask、Silkscreen のいずれかへ対応付けます。生成 path は対応するコンテナへ振り分けられ、fill と stroke が別マテリアルへ対応する shape は両方を残すため分割されます。大きすぎる SVG は従来どおりラスタ画像へフォールバックします。いずれもドキュメントを置き換えず、確認は不要です。ドロップされたファイルが JSON でも認識できる画像タイプでもない場合は、サイレントな no-op ではなく、"Unsupported file" のエラートースト("Drop an image or a zpd panel JSON file.")を表示します。

ペーストも同じ画像/SVG ルーターを使いますが、候補選択は保守的ですpaste ハンドラは、MIME が image/ で始まる、名前が .svg で終わる、または名前と MIME の両方がない OS クリップボードファイルを受け付けます。最後のケースは内容 sniffing に委ねます。その後 routeImportFile() が上記と同じラスタ/ベクター判定を行います。このようなファイルでも、認識できる zpd クリップボードエンベロープでもないペースト内容は、完全にそのまま残されます——エラートーストは出ません。「通常のペーストを決して横取りしない」というペーストの契約全体と整合しています。優先順位の全体については、クリップボード → ペーストを参照してください。

正確に保持されるもの

packages/core/src/serialize.test.tspackages/app/src/editor/download.test.ts は どちらも、5 つのレイヤータイプすべてを含むドキュメントについて、完全なラウンドトリップの 忠実性をアサートします。

doc → serializePanelConfig → JSON.stringify → JSON.parse → parsePanelConfig → doc

parsePanelConfig(JSON.parse(JSON.stringify(serializePanelConfig(doc)))) は、 次を検証するフィクスチャに対して、元の doc と deep-equal になります。

  • すべてのレイヤータイプ(shapepatternpathtextimage

  • 非表示のレイヤー(hidden: true

  • 一部の点にはベジェハンドル(hinhout)があり、他の点にはない path レイヤー

  • path レイヤーの extraSubpaths(画像トレースによる複数の閉じたサブパス)

  • 一部のレイヤーには rotation があり、他のレイヤーにはない状態

  • ドキュメントのルーラー guides(hidden なものを含む。ガイドのラウンドトリップアサーションは roundTripped.guides が元と deep-equal になることを確認します)

上記のすべては、ラウンドトリップを経ても変わらずに残ります。

意図的に保持されないもの

  • 未知・余分なフィールドPanelConfig の形に含まれないもの(余計なトップレベルの キーや、レイヤーごとの追加プロパティ)は、インポート時に破棄されます。 防御的パース を参照。

  • panel.widthMm / panel.heightMm — たとえファイルが手編集で食い違っていても、 読み込み時には常に hp から再計算されます。これらは参考用の出力であり、正式な入力では ありません。PanelConfig フォーマット を参照。

  • 不正な値 — 範囲外の color、有限でない座標、不正な hp などは、そのまま再現される のではなく、クランプまたはデフォルト化されます(当然です——入力が不正だったのですから)。 防御的パース を参照。

  • patternType の妥当性 — 認識できない patternType 文字列は不透明なデータとして ラウンドトリップします(そのまま保持されます)が、そのパターンの描画については何も 保証されません——core はパターンレジストリに依存していないため、ある patternType が 何かに描画されるかどうかを知っているのは app/patterns のレイヤーだけです。

ブラウザレベルでも検証済み

Playwright の @smoke スイートは、純粋な関数だけでなく、ブラウザ上の実際のダウンロードも 検証します。ダウンロードボタンをクリックし、生成されたファイルをディスクから読み込み、 parsePanelConfig(downloaded)parsePanelConfig(onScreen) に等しいことをアサートします。 ここで onScreen は、window.__zpdTest テストブリッジ経由でライブドキュメントから 取得されます。このスイートの構成については、 テスト を参照してください。

Gerber エクスポートはラウンドトリップしない

このページがここまで保証してきたのは、あくまで パネル設定 JSON のラウンドトリップです。 エディタが書き出す成果物はもう 1 つあります——ヘッダーの ⬇ Gerber ボタンと、コマンド パレットの Download Gerber (.zip) が出す Gerber の zip です。同じ「エクスポート」という 言葉で呼ばれてはいますが、この zip は 意図的に終端の成果物 であり、上の保証はこちらには 一切及びません。

ファイル名は zpd-panel-<hp>hp-gerber.zip で、中身は 5 つのエントリです。

  • zpd-panel-<hp>hp.GTL — 表面の銅

  • zpd-panel-<hp>hp.GTS — 表面のソルダーマスク

  • zpd-panel-<hp>hp.GTO — 表面のシルクスクリーン

  • zpd-panel-<hp>hp.GKO — 基板外形

  • README.txt — 上の 4 ファイルとその役割の対応、パネル仕様、そして確認ゲートに出るのと 同じアートワークのみの断り書き。さらに、 Excellon のドリルファイルなし、裏面のファイルなし、ペーストレイヤーなし であることを明記します。

zpd 側に、この zip を読み戻す経路はありません。インポートが受け付けるのはパネル設定 JSON と 画像/SVG だけで(インポートを参照)、Gerber からドキュメントを復元する機能は 存在しません。そもそも Gerber はドキュメントモデルの下流にある表現です。レイヤーの構造も、 パターンのパラメータも、テキストの文字列も、マテリアルごとにブーリアンで統合された平たい 領域へ焼き込まれたあとなので、戻すべき情報がファイルの中に残っていません。 役割はそのように分かれています。保存して読み戻し、発注に使う成果物は JSONすでに仕様の決まったブランクパネルへ渡すアートワークが zip です。zip のほうは、 それ単体で基板を発注できるデータではありません。この zip の元になっているマテリアルモデル については、PCB マテリアルレイヤーとサイズ を 参照してください。

エクスポートの流れ、確認ゲート、そして拒否ダイアログについては、 インターフェース → ダウンロードトリガー → Gerber エクスポート を 参照してください。