ラウンドトリップ
エディタの ⬇ JSON ボタン(packages/)は、現在のドキュメントを 発注可能なパネル設定 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.ts の importJsonFile())に集約されます。そのため、ファイルがどうやって届いたかにかかわらず、「ファイルをインポートする」という挙動は同一です。
ヘッダーの ⬆ JSON ボタン — ref の裏にある、常駐する非表示の
<input type="file">。どこにでもドロップできるインポート(
components/) — 正確なキャンバスのターゲットだけでなく、ページのどこにでもdrop- import. tsx .jsonファイル(あるいは画像——下記参照)をドロップできます。ファイルがウィンドウ上にドラッグされている間、ページ全体を覆うオーバーレイ("Drop image or panel JSON")が現れます。これは、カーソルがネストした要素をまたいでもオーバーレイがちらつかないよう、enter カウントを均衡させたdragenter/dragleaveのペアで追跡されます。ファイルのドラッグだけが捕捉され(dataTransfer.types.includes('Files'))、レイヤー名変更フィールドのようなアプリ内のフィールドへ選択したテキストをドラッグする操作は、完全にそのまま残されます。コマンドパレットの Import JSON コマンド(chordless、パレット専用) — 一時的なネイティブファイルピッカーを開きます。
インポートのパイプラインは 厳格 です。このページの他の場所で使われている寛容な parsePanelConfig() とは異なり、生の JSON を core の tryParsePanelConfig() に通します。これはエンベロープのマーカー(app === 'zpd'、サポート範囲内の version、layers 配列)を 先に チェックしてから、防御的パースで説明した、同じフィールドごとの防御的パースへ委譲します。この使い分けが存在するのは、インポート 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/ と packages/ は どちらも、5 つのレイヤータイプすべてを含むドキュメントについて、完全なラウンドトリップの 忠実性をアサートします。
doc → serializePanelConfig → JSON.stringify → JSON.parse → parsePanelConfig → docparsePanelConfig(JSON.parse(JSON.stringify(serializePanelConfig(doc)))) は、 次を検証するフィクスチャに対して、元の doc と deep-equal になります。
すべてのレイヤータイプ(
shape、pattern、path、text、image)非表示のレイヤー(
hidden: true)一部の点にはベジェハンドル(
hin/hout)があり、他の点にはない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 エクスポート を 参照してください。