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

検索したい単語を入力

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

ジェネレータ契約

@zpd/patterns は自己完結したパッケージです。PanelPatternGenerator の契約、手作業で列挙した組み込みのレジストリ、そしてサムネイルレンダラーからなります。このページでは、すべてのパターン — 組み込みでも新規でも — が実装しなければならない契約を解説します。

PanelPatternGenerator

interface PatternParamDef {
  key: string;
  label: string;
  min: number;
  max: number;
  step: number;
  defaultValue: number;
}

interface DrawOptions {
  widthMm: number;   // draw-region dimensions in millimetres — a pattern
  heightMm: number;  // layer's own square (see below), or a 30mm thumbnail window
  color: string;     // a single palette hex the caller chose for this pattern
  params: Record<string, number>; // keyed by PatternParamDef.key
}

interface PanelPatternGenerator {
  name: string;           // stable kebab id, e.g. 'dot-grid'
  displayName: string;    // human-facing label
  paramDefs: PatternParamDef[];
  draw(ctx: CanvasRenderingContext2D, opts: DrawOptions): void;
}

スケール済み・クリップ済みの mm 空間で描く

draw(ctx, opts) は、呼び出し側 がすでにキャンバスの 1 単位 = 1mm となるようスケールし、描画領域自身の原点へ平行移動し、さらにその領域の矩形 (0,0)(widthMm, heightMm) にクリップした CanvasRenderingContext2D とともに呼ばれます。ジェネレータはオブジェクトローカルな mm 座標で直接描画します — ctx.arc(x, y, radius, ...)x / y / radius はすべて mm で、draw() の内側での単位変換は一切ありません。

ここには、より大きなキャンバス / スライス / ビューポートといった間接化は意図的に ありません。パターンは、与えられたスパンの内側だけを、そのスパン自身の widthMm / heightMm を使って計算します。パターンレイヤーの描画領域は、必ずしもパネル全体ではない、それ自身の 正方形 です——widthMm == heightMm == layer.size。パターンの xysize は、ユーザーがキャンバス上でドラッグやリサイズできる独立したジオメトリであり(エディタ → ツール → パターンの正方形を参照)、draw() はその正方形自身の一辺を widthMm / heightMm にセットして呼ばれます——パネルを覆うデフォルトは、あくまで正方形の 初期 配置にすぎず、draw() が前提にしてよいプロパティではありません。これは、レンダラーの mm 空間レイヤーパスが用意するのと同じ、領域ごとの変換です(正方形自身の原点へ平行移動してからそこへクリップする——メインのパネルクリップの内側にある、独立した合成クリップです)。そして、サムネイルレンダラーがピッカーのプレビュー用に用意する同じ 30mm のスケール済みウィンドウでもあります。パターンの draw() は、2 つのコンテキストのどちらが呼び出しているかも、正方形が現在どれだけの大きさかも、知る必要も気にする必要もありません。

決定論的 — どこにもランダム性はない

描画は決定論的です。同一の入力は同一のピクセルを再現しなければならず(どこにもランダム性はない)、これによりエクスポートされた注文 JSON は忠実に再生できます。

これはスタイルの好みではなく、厳格な制約です。エクスポートされる注文 JSON は、ラスタライズされた画像ではなく、パターンレイヤーの patternTypeparams を保存します。もしジェネレータが Math.random() を呼んでいたら、同じ JSON を後で再生したとき — あるいは同じドキュメントを別のマシンで再レンダリングしたとき — 見た目の異なるパネルが生まれてしまいます。すべての組み込みは、見かけ上の有機的なばらつき(波の位相、光線の角度、格子のタイリング)を、ランダムシードではなく、パネル自身の寸法と宣言されたパラメータをもとにした純粋な三角関数で実現しています。

共有ヘルパー: param-utils.ts

すべての組み込みは、クランプとレイアウトの計算をパターンごとに再実装するのではなく、packages/patterns/src/param-utils.ts からインポートします。

  • resolveParam(params, defs, key) — 後述するクランプです。

  • centeredStart(span, pitch)中央揃えでオーバースキャンする格子を参照してください。

  • hash01(ix, iy, channel, salt) — pgen から移植したパターンにあるセルごとの rand() 呼び出し(タイルの向き、セルのスキップ、ジッター——局所的で独立した選択のみで、決してシーケンス依存のシミュレーションではありません)の決定論的な代替です。描画スパン自身の中心から測ったセルのインデックスをキーにするため、パターンの正方形をリサイズしても、すべてのセルを再シャッフルすることなくタイリングが再センタリングされます。channel は 1 つのセル内の独立した選択を分け、salt はバリアントを分けます。純粋な Math.imul 整数ミックスに murmur3 風のファイナライザを組み合わせたもので——浮動小数点をミックスに含まず、[0, 1) にほぼ一様な出力を返し、(前述の決定論的のとおり)決して Math.random() は使いません。

中央揃えでオーバースキャンする格子

ほとんどの組み込みは、繰り返し単位(ドット、六角セル、レンガ)を自身の描画スパン全体にタイリングします。param-utils.ts の共有ヘルパー centeredStart(span, pitch) は、タイリングがそのスパンに対して 中央揃え になる(1 つの目盛りが必ずちょうど span / 2 に来る)ように反復を開始する最小の格子座標を計算し、さらに両方向で端の外まで オーバースキャン します(ループ境界 span + pitch と組み合わせて)。中央揃えとオーバースキャンによって、どんなスパンでも、あらゆるパターンが意図してデザインされたように見え、見切れたり中心からずれたりしないのです——そのスパンが、パネルを覆うデフォルトの正方形であっても、ユーザーがリサイズしたものであっても、固定 30mm のサムネイルウィンドウであっても同じです。

パラメータのクランプ

すべての draw() は、パラメータを params[key] で直接ではなく、resolveParam(params, defs, key) を通して読み取ります。

function resolveParam(params, defs, key) {
  const def = defs.find((d) => d.key === key);
  if (!def) {
    throw new Error(`resolveParam: unknown parameter key "${key}"`);
  }
  const raw = params[key];
  const finiteRaw = typeof raw === 'number' && Number.isFinite(raw);
  const value = finiteRaw ? raw : def.defaultValue;
  return Math.min(def.max, Math.max(def.min, value));
}

resolveParam は、入ってくる値が欠けているか非有限のときは def の defaultValue にフォールバックし、その後つねに [min, max] にクランプします。このクランプは見た目のためではありません。正のピッチ/カウントを保証するもので、呼び出し側が古い値・ゼロ・負の値を渡しても、すべての描画ループが有限のままであることを保ちます(ゼロや負のピッチは、さもなければ for ループを永遠に回してしまいます)。

要求する keydefs に存在しなければなりません。宣言されていないキーを要求することはジェネレータ側のプログラミングエラーなので、params 側の値が欠けている場合、有限の場合、非有限の場合のいずれでも、resolveParam は必ずそのキー名を含むエラーを送出します。宣言されていないキーが 0、生の値、NaN のいずれかへフォールバックすることを前提にしてはいけません。定義がなければ、強制できる範囲の不変条件もないためです。

ファイル構成

初期の単一ファイル patterns.ts とは異なり、このパッケージは今や 60 個超のジェネレータを、パターン 1 個につき 1 モジュールという単位でシャードに分けて配置しています。

構成要素パス
パターン 1 個につき 1 モジュールpackages/patterns/src/patterns/<name>.ts(1 つの PanelPatternGenerator の const をエクスポート)
グループシャードファイルpackages/patterns/src/patterns/group-*.ts(例: group-japanese.tsgroup-curves.ts — それぞれ、メンバーのパターンモジュールからのインポートを集めた PanelPatternGenerator[] をエクスポート)
レジストリpackages/patterns/src/patterns/index.ts — 手作業で列挙。詳細は後述
共有ヘルパーpackages/patterns/src/param-utils.ts — 前述の共有ヘルパーを参照

レジストリ

patterns/index.tsPATTERN_GENERATORS は、生成されるものではなく 手作業で列挙した配列 です。エディタのツール/インスペクタにあるようなプラグイン/自動検出の仕組みはここにはありません。最初にオリジナルの 12 個の手書きジェネレータを直接列挙し、続いて各グループシャードがエクスポートする配列を、決まった順序で展開(スプレッド)します。

export const PATTERN_GENERATORS: PanelPatternGenerator[] = [
  dotGrid, diagStripes, gridLines, concentricCircles, hexLattice, checker,
  waveLines, crosshatch, radialBurst, brick, diamondLattice, scallops,
  ...groupJapanese,
  ...groupOrnament,
  ...groupCurves,
  ...groupRingsCircuits,
  ...groupTilings,
];

Warning

'dot-grid' はこの配列の先頭に留め、その name を正確に保たなければなりません。コアのデフォルトドキュメント(新しいエディタセッションが開始時に読み込むデモドキュメント)が、これを直接参照しています。

レジストリの上には、2 つの小さなヘルパーがあります。

  • patternByName(name)name でジェネレータを探し、なければ undefined

  • defaultParams(name) — 指定した名前のジェネレータが宣言するすべてのパラメータについて { [key]: defaultValue } を返します(名前が解決しなければ空オブジェクト)。追加したばかりのパターンレイヤーや、すべてのサムネイル描画は、これをもとに params を初期化します。

すべての登録済みパターンの完全なパラメータ表については、組み込みカタログを参照してください。

新しい組み込みパターンを追加する

新しい組み込みのほとんどは、ゼロから書き起こすのではなく、pgen から静的なジェネレータを移植する形でやってきます。.claude/skills/port-pgen-patterns/SKILL.md が、その繰り返し可能なワークフローです。候補の選別を失格条件のチェックリストに照らして行い(グラデーション、複数色が本質的な見た目、シーケンス依存のランダム性は、このページの契約上いずれも対象外です)、mm 空間 / resolveParam / centeredStart / hash01 という移植の慣習、新しいパターンがどのグループシャードに加わるか(あるいは新しいシャードを作るべきタイミング)、そして packages/patterns/pgen-port-ledger.json への行の追加までを扱います。この台帳は、試みたすべての移植(portedoriginalrejected)の追記専用の記録で、scripts/check-ledger.mjs がレジストリと突き合わせて検証します。台帳がどう見えるか、そしてドキュメントの表とどう同期させているかについては、組み込みカタログ → pgen 移植の由来を参照してください。