Skip to content

Latest commit

 

History

History
1613 lines (1117 loc) · 77.4 KB

File metadata and controls

1613 lines (1117 loc) · 77.4 KB

Daiza(アクリルフィギュア台座設計ツール) 実装指示書

概要

TypeScriptを用いて、PNG画像からアクリルフィギュアの重心を解析し、差込口位置・台座サイズを自動計算するWebアプリケーションを実装してください。

本アプリケーションはGitHub Pages(github.io)上で公開できる静的Webアプリとして実装し、サーバーを必要としない構成としてください。

画像解析・幾何計算・SVG生成はすべてブラウザ内で完結させてください。

UIは各種パラメータをリアルタイムに変更でき、結果が即座に反映されることを重視します。


技術構成

フレームワーク

  • React
  • TypeScript
  • Vite

UI

  • React
  • Tailwind CSS
  • shadcn/ui

描画

  • SVG
  • Canvas API(必要に応じて)
  • Three.js + React Three Fiber(3Dプレビューモード。dynamic import で初期バンドルから外す)

画像処理

  • Canvas ImageData

幾何計算

必要に応じて以下を利用可

  • gl-matrix
  • polygon-clipping
  • martinez-polygon-clipping

SVG生成

DOM APIまたは独自実装

外部ライブラリ追加可


ディレクトリ構成

src/

App.tsx

components/

    LeftPanel.tsx
    Preview.tsx
    ResultPanel.tsx

analysis/

    imageLoader.ts
    contour.ts
    centroid.ts
    slot.ts
    base.ts
    stability.ts

render/

    overlay.ts
    simulation.ts

export/

    svg.ts

model/

    state.ts
    types.ts

hooks/

    useAnalysis.ts

utils/

    geometry.ts
    image.ts

UI・解析・描画・エクスポートを明確に分離してください。


UI仕様

画面は左右3ペイン構成(左:パラメータ/中央:プレビュー/右:解析結果)とします。

+---------------------------------------------------------------------------+
|              |                                        |                    |
| 左パネル      |  中央(プレビュー)                      | 右パネル(解析結果) |
|              |                                        |                    |
| ・PNG画像読み込み |  画像プレビュー                        | 画像サイズ          |
| ・フィギュア高さ(mm) |  オーバーレイ表示                    | 実寸               |
| ・板厚(mm)     |  ルーラー(上端・左端)                   | 重心座標(mm)        |
| ・カットライン余白(mm) |  ズーム/パン/Fit/100%           | 差込口中心・差込口幅   |
| ・カットライン平滑化 |                                    | 台座幅・台座奥行     |
| ・隙間埋め閾値(mm) |                                      | 転倒角(左)/(右)    |
| ・パーツ連結部の最小幅(mm) |                              | 転倒角(前)/(後)    |
| ・差込口幅(mm)  |                                        | 転倒角(最小・方位)   |
| ・差込口オフセット(mm) |                                  |                    |
| ・差込口の前後オフセット(mm) |                             |                    |
| ・首部幅(mm)   |                                         |                    |
| ・アクリル板の持ち上げ量(mm) |                             |                    |
| ・台座形状(矩形/角丸/円形/楕円/正多角形/任意形状) |         |                    |
| ・台座寸法(形状別:幅・奥行・直径・角丸半径・辺数 等) |      |                    |
| ・エクスポート(SVG / .ai) |                              |                    |
+---------------------------------------------------------------------------+

パラメータのカテゴリ分け

左パネルのパラメータは十数個あり、平坦に並べると目的の項目を探しづらいため、 どの部品を決める値かで 4 つのカテゴリへ束ね、折りたたみ可能な見出し付きの セクション(アコーディオン)として表示してください。

カテゴリ 含めるパラメータ
アクリル板 アルファ閾値/フィギュア高さ/板厚/カットライン余白/カットライン平滑化/隙間埋め閾値/パーツ連結部の最小幅
差込口 差込口幅/差込口オフセット/差込口の前後オフセット
首部 首部幅/アクリル板の持ち上げ量
台座 台座形状/台座形状ソース(任意形状のみ)/台座寸法(形状別)
  • 「アクリル板の持ち上げ量」は板の浮き=首部の高さそのもの(台座上面 = カットライン 最下端 + 持ち上げ量)なので、アクリル板ではなく首部へ置きます。
  • 初期状態ではすべてのカテゴリを開いた状態とし(初見で全項目が見えることを優先)、 折りたたみは「いま触らない分類を畳む」任意操作として提供します。複数を同時に開けます。
  • カテゴリ分けは表示上のグルーピングのみであり、パラメータの意味・既定値・解析への 影響は変わりません。

解析結果パネルの配置

解析結果パネルはプレビューの下ではなく右側へ置いてください。

一般的なアスペクト比(16:9 等)の横長ウィンドウでプレビューの下に結果一覧を積むと、 プレビューの縦幅が圧迫されて形状が見づらくなるためです。結果パネルを右側の独立した 列にすることで、プレビューはビューポートの縦幅をほぼ使い切れます。

  • 左パネル・右パネルは固定幅(内容が溢れる場合は各列で縦スクロール)
  • 中央のプレビューが残り幅・残り高さをすべて使って伸びる

レスポンシブ対応とし、画面幅が狭い場合は縦積み(左パネル → プレビュー → 解析結果)へ 切り替えてください。


入力

PNG画像

条件

  • RGBA画像
  • α=0を透明
  • α>0をアクリルとみなす(既定。判定境界はアルファ閾値パラメータで変更できる)

対応

  • ドラッグ&ドロップ
  • ファイル選択

画像はブラウザ内のみで処理し、外部へ送信してはいけません。


パラメータ

アルファ閾値

単位

なし(不透明度の割合:0〜1 の小数)

0.00(既定:α>0 をすべてアクリルとみなす)
0.05(アンチエイリアスのごく薄い縁を無視する)
0.50(半透明の影・グローを無視し、絵柄の芯だけを残す)

不透明領域(アクリル)の判定境界を、不透明度の割合で指定します。

アクリル画素 ⇔ α > アルファ閾値 × 255

UI はスライダーで指定し、現在値(小数第2位まで)を併記してください(刻みは 0.01)。

  • 既定値 0 は「α=0 を透明・α>0 をアクリル」(入力の既定規則)と一致します。
  • 上げると、アンチエイリアスの縁・薄いグロー・影といった半透明の画素が不透明領域から外れ、 カットラインがより絵柄の芯へ沿います。
  • 判定が「閾値より大きい」であるため、1 では α=255 すら満たさず全画素が透明になり解析できません。 上限は 0.99 とします。
  • 不透明領域の定義そのものを変えるパラメータなので、絵柄の高さ(px)(=スケールの基準)・ カットライン・重心・差込口・台座のすべてがこの値に依存します。閾値を上げた結果として 不透明画素が 1 つも残らない場合は、全透明画像と同じく解析不可としてエラー表示してください。

実装上の注意:不透明判定がパラメータ化されたことで、二値マスクは画像不変量ではなくなります。 画像単位でキャッシュしてよいのは α プレーン(α 値そのもの、1バイト/画素)までで、二値化は パラメータ側の解析(第2相)で行い、閾値を鍵にメモ化してください(閾値の変更で画像の再読み込み・ RGBA の再転送が起きてはいけません)。


フィギュア高さ

単位

mm

160

接地面(台座の底面)からカットライン(絵柄+カットライン余白)の上端までの全高を指します。 ルーラーの Y 原点は接地面なので、カットライン上端にあたるルーラーの目盛りがそのままこの値に なります。

スケール mm_per_pixel は次のように求めてください(画像高さではなく絵柄の高さが基準)。

絵柄の外側の高さ = カットライン余白 × 2 + アクリル板の持ち上げ量 + 板厚
絵柄の高さ(mm)  = フィギュア高さ − 絵柄の外側の高さ
mm_per_pixel   = 絵柄の高さ(mm) ÷ 絵柄の高さ(px)
  • 絵柄の高さ(px) は不透明領域の上端 Y と下端 Y の差(点間距離)とする。
  • 画像高さを使ってはいけません。PNG の透明余白の量でフィギュアの実寸が変わってしまうためです。
  • 「絵柄の外側の高さ」の内訳は、絵柄のにカットライン余白、絵柄のにカットライン余白+ 持ち上げ量+板厚(=台座の厚み)。カットラインは絵柄の上下どちらへも余白ぶん広がるため、 余白は 2 回効く。
  • フィギュア高さがこの値以下の場合、絵柄が存在できないためスケール計算不可として エラー表示してください。
  • 画素量子化に由来する誤差(余白の膨張が画素格子へ丸められるぶん。上下あわせて 2px 未満)は 許容します。

板厚

2mm

3mm

5mm

カットライン余白

実際のアクリルフィギュアは、絵柄(不透明領域)の外側にある程度の余白を取ってカットするのが一般的です。

そのため、不透明領域の境界をそのままアクリル外形とするのではなく、 指定した余白(mm)ぶん**外側へオフセットした線をカットライン(アクリル外形)**として扱ってください。

単位

mm

範囲

0〜10mm 程度

初期値

3

余白は重心解析・台座計算にも反映されます(オフセット後のカットラインを外形として用いる)。


カットライン平滑化

不透明領域の境界をそのまま用いると輪郭が細かく波打つため、平滑化の強さを指定できるようにしてください。

値を大きくするほどカットラインが滑らかになります。

平滑化は余白オフセットと組み合わせて、最終的なカットライン形状を決定します。

初期値は平滑化最小(または無効)とし、UI操作で強められるようにしてください。

曲線補完(ベジェ等)

平滑化しても輪郭が**折れ線(直線の連なり)**のままだと、カクついた見た目・カット結果になります。

平滑化後の頂点列をベジェ曲線(またはCatmull-Rom等のスプライン)で曲線補完し、滑らかなカットラインを生成してください。

  • オーバーレイ描画・SVGエクスポートともに曲線として出力する(SVGは CQ などの曲線コマンドを用いる)。
  • 平滑化を無効にした場合でも、必要に応じて軽い曲線補完を適用してよい。

例外:差込部の肩は曲線補完しない

カットラインは絵柄の外形と差込部(首部・ツメ)を一体化した 1 本のパスですが、 差込部の肩(首部とツメの接合部)だけは曲線補完せず、直角のまま出力してください。

対象は台座上面ライン上にできる 4 つの角(左右それぞれの「首部下端の角」と「ツメ根元の角」)です。

      │ 首部        ┌─ 首部下端の角
      └────┐ ←──────┘
           │ ←─ ツメ根元の角(首部とツメの接合部)
           │ ツメ

これらは台座と噛み合う機能面の直角であり、丸めると

  • ツメの根元が太り、スリット(幅 = 差込口幅)へ入らない
  • 肩が台座上面へ密着せず、挿入深さのストッパーとして働かない

という実害が出ます。絵柄由来の角は従来どおり曲線補完し、この頂点だけを除外してください。


隙間埋め閾値

カットライン同士(アクリル外形のエッジ同士)の距離が極端に近いと、その部分のアクリルが細く 残ってカット時・使用時に破損する危険があります。

そこで、カットライン間の隙間の幅がこの閾値(mm)より狭い箇所を検出し、その隙間をアクリルで 埋める(カットラインが隙間の外側をなめらかに回り込む形にする)機能を設けてください。

分離パーツ間の狭い隙間だけでなく、同一パーツ内のくびれ(凹部が向かい合って狭くなる箇所) にも同様に働きます。

単位

mm

範囲

0〜20mm 程度

初期値

3
  • 細く残ったアクリルは印刷・加工で潰れやすいため、既定で埋まっている状態を出発点とします (幅 3mm 未満の隙間・くびれが充填される)。
  • 0 を指定すると無効になり、クロージングを一切行いません(隙間を埋めたくない場合に用いる)。

アルゴリズムの詳細は「カットライン」節の「狭い隙間の充填(隙間埋め)」を参照してください。


差込口幅

mm

初期値

20

プリセット(標準値の選択肢)は設けず、数値入力のみとしてください。


差込口オフセット

差込口は基本的に重心の真下に配置します。

左右方向の微調整のためのオフセット(mm)を指定できるようにしてください。

  • 正の値で右方向、負の値で左方向へずらす
  • 初期値 0

差込口の前後オフセット

台座に切るスリットの奥行方向(前後)の位置を指定できるようにしてください。

前面図(プレビュー)には現れない上面図の量であり、台座の奥行中心を原点として測ります。

  • 正の値で前(手前)、負の値で後(奥)へずらす
  • 初期値 0(=台座の奥行中心にスリットが来る)

アクリル板は台座のスリットへ差し込まれるため、スリットの位置がそのまま板(=重心)の 奥行位置になります。よってこの値は前後の転倒角を直接左右します(前へ寄せるほど 転倒角(前) が小さく、転倒角(後) が大きくなる)。

スリットの開口幅は板厚(mm)なので、スリットが台座からはみ出す指定は台座計算不可baseCalculationFailed)として扱ってください(「台座サイズの検査」を参照)。


首部幅

差込部は「首部(アクリル板と台座の間をつなぐ矩形)」と「ツメ(台座のスリットへ挿す部分)」の 2 段構成とします(詳細は「差込部の構造」を参照)。

このうち首部の幅(mm) を、差込口幅とは独立したパラメータとして指定できるようにしてください。

単位

mm

初期値

40
  • 首部は重心直下(差込口中心と同じ X)に配置する
  • 首部幅は差込口幅と独立して増減できる(差込口幅を変えても首部幅は変わらない)
  • 上限は設けない(UI の入力欄でも頭打ちにしない)。妥当な首部幅は板の大きさ次第であり、 一律の頭打ちには意味がないため。広げすぎて**首部の左右端がカットライン下辺と交わらなくなった 場合は、差込口が配置不可(slotPlacementFailed)**として弾く(=実効的な上限は板の形状が決める)

制約:首部幅 > 差込口幅

首部幅は必ず差込口幅より大きくしてください。

首部の下端左右にできる肩(ショルダー)が台座上面に当たることで、挿入深さがツメ深さ(板厚)で 止まるという設計です。首部幅 ≦ 差込口幅 だと肩が消え、アクリル板がツメより深く台座へ 刺さり込んでしまうため、この関係は成立していなければなりません。

首部幅 ≧ 差込口幅 + 2 × 最小ショルダー幅
  • 最小ショルダー幅(片側)は 0.5mm 程度を既定とする
  • UI では首部幅の下限を差込口幅に連動させ、上記を割る値を入力できないようにする
  • 差込口幅を広げた結果として首部幅が下限を割る場合は、首部幅を自動的に下限まで押し上げる
  • 何らかの理由で制約が破れた場合は差込口が配置不可(slotPlacementFailed)として扱う

アクリル板の持ち上げ量

アクリル板(カットライン)の下端が台座より下へ潜り込むことがあってはいけません。 板の下端は常に台座上面より上になるように配置してください(詳細は「アクリル板と台座の上下関係」を参照)。

そのうえで、板をさらに上へ持ち上げられるよう、持ち上げ量(mm) をパラメータとして用意してください。

単位

mm

範囲

0〜50mm 程度

初期値

0
  • 0 のとき、アクリル板の下端は台座上面にちょうど接する
  • 値を大きくするほど板は台座上面から浮き、その隙間は首部が埋める

台座幅

台座の実寸の幅(mm) を直接指定します。指定した値がそのまま台座の幅になります (左右余白として 2 倍する、といった加算は行いません)。

範囲

1〜300mm 程度

初期値

50
  • 台座は差込口中心を軸に左右対称へ配置するため、支持範囲は 差込口中心 ± 台座幅/2 となる
  • 指定幅が最低条件(重心が支持範囲内)を満たさない場合、および差込口幅を 内包できない場合は、幅を自動的に広げるのではなく台座計算不可baseCalculationFailed) として扱い、パラメータの見直しを促す
  • 最低条件は「倒れない」の臨界(台座端が重心の真下にちょうど届く)に過ぎず、余裕の大きさは 含まない。どれだけ余裕があるかは転倒角(左)/(右) で判断する(臨界では 0°、幅を広げる ほど大きくなる)
  • 台座形状が円形・正多角形のときは使わない(「台座直径」で指定する)。楕円では左右径、 任意形状では footprint のバウンディングボックスの幅として働く(「台座形状」を参照)

台座奥行

台座の実寸の奥行(mm) を直接指定します。台座幅と同じく、指定した値がそのまま台座の奥行に なります(自動算出はしません)。

範囲

1〜300mm 程度

初期値

30
  • 奥行は前面図には現れない上面図の寸法であり、プレビューの台座(緑矩形)の縦の長さとして 描いてはいけない(「台座(緑矩形)の縦の長さ」を参照)
  • 台座には幅 = 板厚 のスリットを切るため、奥行はスリットを内包できる必要がある (「台座サイズの検査」を参照)
  • 前後の倒れにくさは奥行と前後オフセットで決まる。どれだけ余裕があるかは 転倒角(前)/(後) で判断する
  • 台座形状が円形・正多角形のときは使わない(直径から決まる)。楕円では前後径、任意形状では footprint のバウンディングボックスの奥行として働き、ソース読込時にアスペクト比から 自動設定される(「台座形状ソース(任意形状)」を参照)

台座形状

台座の footprint(上面図の外形)の種類を選択できるようにしてください。

種類

矩形(既定)
角丸矩形
円形
楕円
正多角形
任意形状(PNG シルエット / SVG パス)

形状ごとに寸法の指定に使うパラメータが異なります。UI(左パネル)は選択中の形状に該当する パラメータ欄だけを表示してください。

  • 矩形 … 台座幅 × 台座奥行
  • 角丸矩形 … 台座幅 × 台座奥行 + 角丸半径
  • 円形 … 台座直径
  • 楕円 … 台座幅(左右径) × 台座奥行(前後径)
  • 正多角形 … 台座直径(外接円) + 辺数 + 回転角
  • 任意形状 … 台座幅 × 台座奥行(バウンディングボックス) + 台座形状ソース

台座形状は表示のみの切替ではなく解析パラメータです。成立検査・転倒角・プレビュー・3D・ エクスポートのすべてが選択された footprint に追従します(「台座 footprint(形状の幾何)」節を 参照)。既定は矩形とし、矩形選択時の挙動は従来仕様と完全に一致しなければなりません (検査式・転倒角・プレビュー・エクスポート・3D のいずれにも差が出ないこと)。


角丸半径

角丸矩形の四隅の丸め半径。

単位

mm

範囲

0〜150

初期値

5
  • 0 で矩形と一致する
  • 上限は min(台座幅, 台座奥行) / 2。これを超える指定は reducer(normalizeParameters)で 常に上限へクランプする(首部幅の下限連動と同じ扱い)。台座幅・奥行の変更で上限を割った 場合も同様にクランプする
  • 半径が上限に達すると短辺側が半円になる(スタジアム形。台座幅 = 台座奥行なら円と一致する)

台座直径

円形・正多角形で用いる直径。

単位

mm

範囲

1〜300

初期値

50
  • 円形では footprint の直径そのもの
  • 正多角形では外接円の直径とする。結果パネルの「台座幅・台座奥行」には footprint の バウンディングボックス実寸を表示する(辺数・回転角によっては直径より小さくなる)

正多角形の辺数・回転角

  • 辺数:3〜12 の整数。初期値 6。非整数・範囲外の値は reducer で丸める
  • 回転角:−180〜180°、刻み 1°。初期値 0°
    • 0° で前(手前)側に 1 辺が正対する(前縁の辺の中点が奥行軸上に来る)
    • 正の回転の向きは方位角(「転倒シミュレーション」節:右 0° → 前 90°)と同じとする

台座形状ソース(任意形状)

任意形状の footprint はユーザーが読み込むファイルから作ります。PNG シルエットSVG パスの 2 系統に対応してください。いずれもブラウザ内のみで処理し、外部へ送信しては いけません(完全クライアントサイドの制約)。

読み込み UI は左パネルのファイル選択のみとします(プレビュー領域へのドラッグ&ドロップは フィギュア画像の読み込みに予約済みのため、台座形状には使わない)。

PNG シルエット

  • RGBA PNG を読み込み、固定しきい値 α > 127 で二値化した不透明領域を台座領域とみなす。 絵柄用の「アルファ閾値」パラメータは適用しない(シルエットのアンチエイリアス縁は 不透明度 50% を境界に取るのが知覚的に正しく、絵柄とは判定の目的が異なるため)
  • 分離した複数パーツがある場合は最大面積のパーツのみを採用する(小さな飛び地・ごみ画素を 無視できる)。パーツ内部の穴(透明領域)は無視し、外輪郭のみを用いる(穴あき台座は将来拡張)
  • 輪郭抽出は既存の輪郭追跡を再利用し、軽い平滑化+曲線補完(固定強度。SPEC 根拠のない 調整可能な定数とする)で画素段差を消す
  • 不透明画素が 1 つも無い・輪郭が抽出できない場合は「台座形状が利用できない」 (baseShapeFailed)として扱う

SVG パス

  • <path> および基本図形(rect / circle / ellipse / polygon)を対象とし、 最大面積の閉じたサブパスを外輪郭として採用する(穴は無視する)
  • パスの解釈はブラウザの SVG 実装へ委ねる:非表示の SVG 要素を DOM へ一時挿入し、 getTotalLength() / getPointAtLength()十分な密度に折れ線サンプリングする (独自のパスパーサを書かない。曲線の種類を問わず、正確に元曲線上の点が得られる)
  • サンプリング後は PNG 由来と同じく曲線補完を通す(サンプル点は元曲線上にあるため、 密度が足りていれば偏差は許容誤差内に収まる)
  • transform 属性は非対応(無視する)。必要ならエクスポート元でトランスフォームを適用した パスへ整形してから読み込んでもらう。閉じたパスが見つからない・面積 0 の場合は baseShapeFailed として扱う

正規化とスケール

  • 読み込んだ輪郭はバウンディングボックスで正規化して保持し、台座幅 × 台座奥行へ 非等方スケールして footprint にする(元ファイルの実寸・単位は使わない)
  • 読み込んだ時点で、台座奥行をソースのアスペクト比に合わせて自動設定する(台座幅は維持)。 以後は幅・奥行を独立に変更できる(引き伸ばしを許す)
  • 自己交差を含む輪郭は union / simplify で単純化し、単純化後の最大面積リングを採用する
  • ソースは正規化済みの折れ線(数百〜千頂点程度)として保持する。ピクセルデータを保持し続けては ならない(ImageData を React の state に載せない制約と同じ)
  • 「任意形状」を選択していてソースが未読込の間は baseShapeFailed として扱う

カットライン(アクリル外形)

アクリルの外形は不透明領域の境界そのものではなく、以下の手順で決定されるカットラインとします。

  1. 不透明領域(α>0)の輪郭を抽出する(分離した複数パーツがある場合はすべての輪郭を抽出する
  2. カットライン余白(mm) ぶん外側へオフセットする
  3. オフセットにより生じた自己交差を除去し、単純多角形(または妥当なパーツ集合)へ正規化する
  4. 隙間埋め閾値(mm) より狭い隙間を充填する(後述「狭い隙間の充填(隙間埋め)」)
  5. カットライン平滑化 を適用して輪郭を滑らかにする
  6. 平滑化後の折れ線を曲線(ベジェ等)で補完し、滑らかなカットラインとする

以降の重心解析・差込口配置・台座計算・オーバーレイ・SVGエクスポートは、 このカットライン(およびカットラインが囲む領域)を外形として扱ってください。

複数パーツの連結

不透明領域が2つ以上の分離したパーツ(例:本体と離れた小物、途切れた絵柄)で構成される場合、 そのうちの1つだけをアクリル板とみなしてはいけません。

  • すべてのパーツを1枚のアクリルにまとめたカットラインとして扱ってください。
  • 余白オフセットにより近接パーツが結合する場合は、結合後の外形を1枚のアクリルとして扱います。
  • 余白を加えても分離したままのパーツが残る場合は、凸包で全体を緩く包むのではなく、各パーツの 輪郭に沿わせたまま、パーツ同士を細い連結部(ブリッジ)でつないで1枚にしてください。 内部の隙間で本体が分断されないようにしつつ、不透明領域の輪郭を保ちます。
  • 連結部が細すぎるとアクリルの耐久性が落ちるため、連結部の幅は「パーツ連結部の最小幅(mm)」を 下回らないようにしてください。連結の総延長(=余分な材料)が最小になるよう連結します。 この最小幅の初期値は 6mm(板厚 3mm の 2 倍程度。板厚と同程度ではカット時・使用時に 折れやすいため)とします。
  • 連結部とパーツ輪郭の接合部は、隙間埋めの対象です。継ぎ足しただけの尖った接合部にせず、 なめらかにつないでください(後述「隙間埋めと連結部の整合」)。
  • 例外的に連結が破綻する場合のみ、パーツを分断させない安全策として全体の包絡線(凸包)へ退避します。

自己交差の回避

カットライン余白を大きくすると、オフセットした輪郭が自分自身と交差して破綻することがあります。

  • オフセット後は自己交差を検出・除去し、正しい単純多角形(またはパーツ集合)へ正規化してください。
  • 凹部が余白でつぶれる場合も、破綻した形状ではなく妥当な外形になるようにしてください。
  • polygon-clipping / martinez-polygon-clipping 等による union / simplify の利用を検討してください。

狭い隙間の充填(隙間埋め)

隙間埋め閾値(mm) より狭い隙間(カットラインのエッジ同士が向かい合う狭窄部)を、 **モルフォロジカルクロージング(膨張→収縮)**で充填してください。

アルゴリズム

余白オフセット後のアクリル領域(マスク)に対し、半径 r = 閾値 / 2 の円板による クロージングを適用します。

closeMask(mask, r) = erodeMask(dilateMask(mask, r), r)
  • 半径 r の円板クロージングは「半径 r の円板が入り込めない隙間」=幅が閾値(2r)未満の 隙間だけを充填し、それ以外の領域は変化させない(充填の要否判定と充填処理が 単一の操作で同時に達成される)。
  • 充填後の境界は円板の包絡(半径 r の円弧)になるため、隙間は自動的になめらかに補間される。 折れ線状の継ぎ目や尖りは生じない。
  • 分離パーツ間の隙間と、同一パーツ内のくびれ(凹部)のどちらにも同様に働く。

実装方法(既存 EDT 基盤の双対で構成)

余白オフセットに用いている EDT(ユークリッド距離変換)によるマスク膨張と同じ基盤で 実装します。

  • 膨張 = 前景(アクリル)からの距離 ≦ r をしきい値化(既存 dilateMask と同一)
  • 収縮 = 背景からの距離 > r をしきい値化(dilateMask の双対。erodeMask として定義)
  • 膨張で領域が画像枠外へ広がっても欠けないよう、クロージング前にグリッドを r +数px パディングし、収縮後に元座標系へ戻す(既存 dilateMask のパディングと同じ考え方)
  • EDT は O(W×H) のため計算量は既存の余白膨張と同オーダー。閾値 0(無効)のときは クロージング全体をスキップする

パイプライン上の位置・整合

  • 余白オフセット(EDT 膨張)の直後、平滑化の前に適用する。閾値は「最終的なカットライン 間の隙間」として解釈するため、余白反映後のマスクに対して測る。
  • クロージングにより結合したパーツは 1 枚のアクリルとみなす。それでも分離が残るパーツ のみ、従来どおり連結部(ブリッジ)/凸包退避の対象とする。
  • 充填後の外形が最終カットラインとなり、以降の重心解析・差込口配置・台座計算・ オーバーレイ・SVG エクスポートはすべてこれに追従する。

隙間埋めと連結部の整合

隙間埋めはアクリルへ足した材料(差込部の首部・パーツ連結部のブリッジ)を含む最終形状に 対して掛けてください。ブリッジを外形へ継ぎ足すだけでは、接合部が尖ったまま残ります。

  • ブリッジの側面とパーツ輪郭が作る入隅は、接合点へ向かって幅 0 まで細くなる隙間であり、 閾値がいくつであっても必ず「閾値より狭い」区間を含む。したがって通常の狭窄部とまったく 同じ充填対象であり、接合部は半径 r の円弧でなめらかにつながるのが正しい姿になる。
  • 順序は **「隙間埋め → 残った分離パーツをブリッジ連結 → ブリッジを含む形状へ隙間埋めを 掛け直す」**とする。どのパーツが分離したまま残るか(=ブリッジが要るか)はクロージングの 結果を見ないと決まらないため、掛け直しは 2 度目になる。
  • 掛け直しは全面へ再適用する必要はない。円板クロージングは「足した材料から距離 2r 以内」しか 結果が変わらず、かつ各点の結果は「その点から距離 2r 以内」のマスクだけで決まるため、 ブリッジ周辺だけを掛け直せば全面クロージングと同じ結果が得られる(O(W×H) の重い段を 二重に走らせない)。
  • 連結部の幅は「最小幅を下回らない」ことだけが要件なので、マスクへ塗る際の画素量子化は 太る側へ倒してよい(輪郭追跡は画素中心を結ぶため、塗った矩形は 1 辺あたり最大 1px 痩せる)。
  • 隙間埋めが無効(閾値 0)のときは充填の半径が定まらないため、従来どおり多角形のまま ブリッジを合成してよい(材料を足さない指定なので、接合部が尖るのが期待どおりの結果)。

隙間埋めと差込部の整合

隙間埋めは差込部の首部をカットラインへ合流させた後の形状に対して掛けてください。 首部の側面とフィギュア外形の間(脚の内側と首部の間など)にできる狭い隙間も、通常の 狭窄部とまったく同じ扱いで充填対象になります。

  • 差込部の位置は重心(=カットライン)が決まらないと定まらないため、順序は **「カットライン生成 → 重心・台座上面・差込部の配置 → 首部を合流させたマスクで隙間埋めを やり直す」**とする(膨張マスクは 2 回の生成で共有し、再膨張しない)。
  • ツメは充填の対象に含めない。ツメ幅は台座スリットへ挿さる実寸なので、クロージングの円弧で 根元が太るとスリットへ入らなくなる。首部・ツメは充填・平滑化のあとに**本来の寸法で 合成(union)し、丸まった角を実寸へ戻す(union は材料を足すだけなので充填部は失われない)。 合成する首部の形状は矩形ではなく「カットライン下辺の弧と台座上面で囲む多角形」**とする (左右側壁 X・下端 Y は矩形と同一。「首部」節のとおり、矩形の上部が板の外へ張り出すのを防ぐ)。
  • 隙間埋めが無効(閾値 0)のときは、従来どおり多角形のまま首部・ツメを一体化してよい (マスクの再走査を行わない軽い経路)。

表示範囲(見切れ防止)

余白を大きく取ると、カットラインが元画像の枠外へはみ出すことがあります。

  • カットラインが画像範囲外へ広がっても、プレビューで見切れないよう表示範囲を拡張してください。
  • Fit 表示・オーバーレイ・SVG エクスポートの座標範囲は、画像だけでなくカットラインの外接範囲を含むように算出してください。

重心解析

透明以外を均一密度とみなしてください。

重心は画像モーメントで求めます。

Cx = Σx / N

Cy = Σy / N

差込口位置

差込口は基本的に重心の真下へ配置します。

差込口オフセット(mm) により、左右方向へ位置を調整できます。

差込口中心 X = 重心X + 差込口オフセット

差込部の構造(首部+ツメ)

差込部は、幅の異なる 2 つの矩形で構成してください。 差込口幅ぶんの矩形 1 つで重心からツメ先まで通す構造にはしません。

首部はツメより幅が広く、その差分としてできる肩(ショルダー)が台座上面に乗ることで、 挿入深さがツメ深さ(板厚)で止まります。この肩が差込部の設計上の要です。

         ┌─────────────────┐
         │   アクリル板     │   ← カットライン(本体)
         └────┬───────┬────┘
              │  首部  │           幅 = 首部幅(mm) > 差込口幅
              │       │
   ═══════┌───┴──┐┌───┴───┐═════   ← 肩が台座上面に乗って止まる
   台座    │ 肩   ││  肩   │  台座
   ───────┴───┐  └┘  ┌───┴──────  台座上面
              │ ツメ  │           幅 = 差込口幅(mm)
              │       │           深さ = 板厚(mm)
              └───────┘

首部

  • 重心直下(差込口中心 X)に置く矩形
  • 幅は「首部幅(mm)」パラメータで指定する(差込口幅とは別。ただし必ず差込口幅より大きい
  • 上端はアクリル板本体(カットライン)と重なり、下端は台座上面に一致する
  • 「アクリル板の持ち上げ量」で生じた板と台座の隙間、および板下端の凹凸ぶんを埋める役割を持つ
  • 下端左右の肩が台座上面に接し、挿入深さのストッパーとして機能する
  • 首部が埋めるのはカットライン下辺(板の下側輪郭)と台座上面の間だけとする。 板の下辺が首部幅の範囲内で大きく上下する形状では、左右端の交点のうち浅い方へ上端を 合わせた単純な矩形が板の輪郭の外(横)へはみ出すため、下辺より上で板の外へ出る部分は 取り除き、最終カットラインへ含めない

ツメ

  • 首部の先(台座上面より下)に付く矩形
  • 幅は「差込口幅(mm)」(=首部より狭い)
  • 深さは原則としてアクリル板の板厚(mm)と同じとする(=板厚×差込口幅の矩形)
  • ツメが台座を貫通してはいけない。ツメ深さは常に台座奥行(および台座の厚み)の範囲内に収める

カットラインとの一体化

差込部は、必ずしも不透明領域(絵柄)に接している必要はありません。

首部・ツメを含むようにカットラインを下方向へ拡張し、 アクリル板本体・首部・ツメが常に 1 枚の一体形状としてカットされるようにしてください。

拡張後のカットラインを最終的なアクリル外形とし、オーバーレイ・SVGエクスポートにも反映してください。


アクリル板と台座の上下関係

アクリル板(カットライン)の下端が台座上面より下に来る不具合を防ぐため、 以下を常に成り立つ不変条件として扱ってください。

アクリル板(カットライン)の最下端 Y ≦ 台座上面 Y
  • カットライン余白を大きく取ると外形は画像下端より下へ広がるため、 台座上面は「画像下端」ではなくカットラインの最下端を基準に決める。
  • 台座上面 Y = カットライン最下端 Y + アクリル板の持ち上げ量(mm)
    • 持ち上げ量 0 で板の下端と台座上面がちょうど接する
    • 持ち上げ量を増やすと板が台座から浮き、その隙間は首部が埋める
  • 台座上面より下へ出てよいのはツメのみ(深さ=板厚)。板本体が台座へ潜り込んではいけない。

重心高さ(転倒角の計算に使う)は、台座上面(接地面)を基準に測ってください。


台座 footprint(形状の幾何)

「台座形状」パラメータで選ばれた形状は、**footprint(上面図の外形、実寸 mm)**として一元的に 表現してください。成立検査・転倒角・プレビュー・3D・エクスポートはすべてこの footprint に 追従します。矩形はその特殊形です。

ローカル座標系と配置

footprint は台座ローカル座標(原点 = footprint のバウンディングボックス中心、X = 右が正、 Z = 前(手前)が正)で表します。3D シーンの床平面(X・Z 軸)と同じ軸の取り方です。

  • 配置は従来規則の一般化とする:原点 X = 差込口中心 X(台座は差込口中心を軸に置く)、 原点 Z = 台座の奥行原点(従来の「台座の奥行中心」。前後オフセットの原点)
  • スリットはローカル座標 (0, 前後オフセット) を中心に、幅 = 差込口幅(X 方向)・ 開口 = 板厚(Z 方向)の矩形として切られる(従来と同じ)
  • 上面図として描くとき(エクスポート・上面図インセット)は前(+Z)を下へ対応させる (エクスポートの既存規約「上辺が後(奥)・下辺が前(手前)」と一致)

内部表現(パス+折れ線)

footprint は次の 2 表現を併せ持つ 1 つのモデルとして構築してください。

  • 閉パス(直線+3次ベジェのセグメント列)… プレビュー・エクスポートの曲線出力に使う
  • 折れ線(許容誤差 0.05mm で平坦化した頂点列)… 成立検査・転倒角・3D 押し出しに使う

プリセット形状は解析的にパスを生成します。

  • 矩形・正多角形 … 直線のみ
  • 円・楕円 … 4 分割の 3 次ベジェ近似(半径方向の誤差は 0.03% 未満で、加工精度に対して十分)
  • 角丸矩形 … 直線+四分円弧(円弧は円と同じベジェ近似)

任意形状(PNG / SVG 由来)は、正規化済み折れ線を台座幅 × 台座奥行へスケールし、既存の 曲線補完(utils/curve)を通してパスにします。

段の位置とメモ化

footprint の構築はパラメータ依存の第 2 相で行います。頂点数百程度の幾何なので計算は軽く、 カットライン系の重い段(二値化・EDT 膨張・差込部の合流)とは独立です。台座形状・寸法の 変更でカットライン段の再計算が起きてはいけません(現行の「台座幅だけを変えてもどの段も 再計算されない」メモ化粒度を維持する)。


台座サイズの検査

台座の寸法(幅・奥行・直径 等)はいずれもユーザー指定値をそのまま実寸として用います (自動では拡大しません)。その指定寸法・形状で成立するかどうかを、footprint に対する次の 2 つの検査で判定してください(いずれも境界上ちょうどは成立側に倒す)。

スリットの内包

スリット矩形(幅 = 差込口幅、奥行方向の開口 = 板厚、中心 = ローカル座標 (0, 前後オフセット))が footprint の内側に完全に含まれること。含まれない場合はスリットが台座の縁を割ってしまうため、 台座計算不可(baseCalculationFailed)として扱ってください。この検査は同時に、「ツメが台座を 貫通しない(ツメ深さ = 板厚 ≦ 台座奥行)」という要件も満たします。

  • 判定は折れ線 footprint に対して行う:スリット矩形の 4 隅が内部(境界含む)にあり、かつ 4 辺が footprint の輪郭と交差しないこと(非凸形状では隅の内包だけでは不十分なため)
  • 矩形 footprint では従来式と一致する: 台座幅 ≧ 差込口幅、かつ 台座奥行 ≧ 板厚 + 2 × |差込口の前後オフセット|

重心の支持

重心の鉛直投影(ローカル座標で (重心X − 差込口中心X, 前後オフセット)。板はスリットへ 差し込まれるため、投影の奥行位置はスリット中心)が footprint の凸包の内側(境界含む)に あること。外れる場合は静的に自立できないため、台座計算不可(baseCalculationFailed)として 扱ってください。

  • 転倒に抗する支持範囲は footprint の凸包で決まる(非凸形状の凹みは接地の支持範囲を 狭めない)。スリット内包が「材料の中に切れるか」を footprint そのもので見るのに対し、 支持は凸包で見る
  • 矩形 footprint では 台座幅 ≧ 2 × |重心X − 差込口中心X| かつ 台座奥行 ≧ 2 × |前後オフセット| と一致する。従来の必要幅にあった「差込口幅を下限に取る」 条件と、必要奥行の板厚ぶんは、より厳しいスリット内包の検査が受け持つため、両検査を 合わせた成立条件は従来仕様と完全に一致する

余裕の扱い

これらの検査は「静的に成立するか」の可否だけを見ます。倒れにくさの余裕は検査に織り込まず、 転倒シミュレーションが返す転倒角(左)/(右)/(前)/(後)/(最小) で表現してください (重心投影が凸包の境界上にあるとき、その方向の転倒角は 0° になります)。


転倒シミュレーション

左右方向・前後方向について

転倒角

を計算してください。

計算式

θ = atan(支持端距離 / 重心高さ)

左右・前後それぞれ表示してください。

footprint への一般化(支持関数)

台座形状が矩形以外でも同じ定義で計算できるよう、支持端距離は footprint の凸包の 支持関数で定めます。水平の単位方向ベクトル d について

支持端距離(d) = h(d) − ⟨g, d⟩
  h(d) … footprint 凸包の支持関数(凸包の頂点 v にわたる max ⟨v, d⟩)
  g    … 重心の鉛直投影(ローカル座標 (重心X − 差込口中心X, 前後オフセット))

とし、θ(d) = atan(支持端距離(d) / 重心高さ) をその方向の転倒角とします。方向 d へ倒すときの 支点(回転軸)は、凸包を法線 d の直線で支えたときの接触辺・接触点です。

  • 左右前後は d = (−1,0)/(+1,0)/(0,+1)/(0,−1) の特殊形(Z は前が正)
  • 矩形 footprint では h(±X) = 台座幅/2、h(±Z) = 台座奥行/2 となり、従来式と厳密に一致する
  • 「台座サイズの検査」(重心が凸包内)を通っていれば、どの方向の支持端距離も非負になる

最小転倒角(最悪方向)

4 方向に加えて、全方位で最小の転倒角とその方位を求めて表示してください。非対称な footprint(正多角形・任意形状)では最悪方向が斜めになり得るため、左右前後だけでは 見落とします(矩形・円・楕円など対称形では 4 方向の最小と一致します)。

全方位の最小は探索不要で閉形式に落ちます:支持端距離(d) の d にわたる最小値は 重心投影 g から凸包の最近傍辺(の載る支持直線)までの距離に等しく、最悪方位は その辺の外向き法線です。

最小転倒角 = atan( min_辺 dist(g, 辺の支持直線) / 重心高さ )
  • 方位角は右 0°・前 90°・左 180°・後 270°と定める
  • 結果パネルには最小転倒角の値と方位(方位角+8方位の目安ラベル。例「135°(左前)」)を 表示する。矩形でも表示する(4 方向の最小と一致する)
  • 2D の転倒シミュレーション表示は従来どおり左右の限界姿勢のみを対象とする(前面図に奥行が 現れないため。最小転倒角は数値と上面図インセットの方位矢印で示す)
  • 3D の傾けは任意の方位を対象とする(「転倒シミュレーション(3D)」節)。倒す向きを方位角で 与えるため、最悪方位での転倒をそのまま再現できる。左右・前後の 2 軸で与えてはならない: 斜めの姿勢が 2 軸回転の合成になり、支点も転倒角も方位ごとの定義(支持関数)と食い違う

前後方向の幾何

前後方向は画像に写らないため、台座の奥行中心を原点とする 1 本の軸(正=前/負=後)として 扱います。

  • 支持端は台座の前縁・後縁、すなわち ±(台座奥行 / 2)(矩形の場合。一般の footprint では 凸包の前後端 h(±Z))
  • アクリル板は台座のスリットへ差し込まれるため、重心の奥行位置=スリット中心 (=差込口の前後オフセット)となる
  • 重心高さは左右と同じ値(重心は板の面内にあり、奥行方向へ傾けても高さは変わらない)
支持端距離(前) = 台座奥行/2 − 差込口の前後オフセット
支持端距離(後) = 台座奥行/2 + 差込口の前後オフセット

UI上の注意

転倒シミュレーションの操作ボタン/トグルには、リロード(更新)と誤認されるアイコンを用いないでください。

適切なアイコンが無い場合は、アイコンを使わず「転倒シミュレーション」等のテキストラベルを表示してください。


プレビュー

画像上へ以下をオーバーレイ表示してください。

  • 外形(半透明)
  • 重心(赤丸)
  • 差込部:首部(青矩形)/ツメ(青矩形)
  • 台座(緑矩形)
  • 支持範囲(オレンジ線)
  • 重心からの鉛直線(点線)

すべてリアルタイム更新してください。

重心マーカーの大きさ

重心(赤丸)のマーカーは大きすぎると形状が隠れて見づらいため、従来の 50% 程度の大きさに 抑えてください(半径を約半分にする)。

台座(緑矩形)の縦の長さ

プレビューは前面図であり、台座の奥行(mm)は上面図の寸法なので、これを前面図の縦寸法として 描いてはいけません(重心高さ由来の大きな値がそのまま縦長の帯になり、形状を覆い隠します)。

表示上の台座の縦の長さ = アクリル板の板厚(mm) = 差込ツメの縦の長さ

とし、上辺を台座上面に合わせて描いてください。ツメがちょうど台座に収まって見えることで、 貫通していないことも同時に確認できます。

台座形状が矩形以外でも前面図の描き方は同じで、横幅は footprint のバウンディングボックス幅 (=凸包の左右端。支持範囲のオレンジ線と一致する)を用いてください。奥行方向の形状は 前面図には現れないため、上面図インセット(次項)で確認できるようにします。

上面図インセット(台座形状の確認)

前面図には台座の奥行・形状が現れないため、プレビューの隅(右下)に小さな上面図を オーバーレイ表示してください。

  • 描くもの:footprint(台座と同じ緑)/スリット(青)/重心の鉛直投影(赤点)/ 最小転倒角の方位矢印(重心投影から最悪方位へ)
  • 向きは前(+Z)が下(エクスポートの上面図と同じ規約)
  • トグルで表示/非表示を切り替えられるようにする。既定は台座形状が矩形のとき OFF、 それ以外のとき ON(ユーザーのトグル操作を優先する)
  • 表示のみであり、解析結果・パラメータ・エクスポートには影響しない
  • ルーラー・解析中インジケータ・エラー表示と重ねない。パン・ズームの操作(ドラッグ・ ホイール)を妨げない
  • 3D モード中は表示しない

解析中インジケータの配置

パラメータ変更にともなう再解析中に表示する「更新中…」インジケータは、プレビュー上端の ルーラーと重なってはいけません

  • ルーラー帯(上端)の下端よりも下へ配置し、目盛り・数値ラベルを隠さないようにする
  • 初回解析時の全面インジケータ(「解析中…」)は従来どおりでよい

完成プレビューモード

プレビューには、通常の解析表示モードに加えて、アクリルフィギュアの仕上がりを確認する 完成プレビューモードを用意し、トグルで切り替えられるようにしてください。

本モードは表示のみの切替であり、解析結果・パラメータ・SVG エクスポートの内容には 一切影響を与えません。

有効時は以下のように見た目を変更します。

  • 絵柄の上のオーバーレイを無効化する
    • 現在、外形(カットライン)領域に載せている薄い水色(半透明)の塗りのうち、 絵柄(不透明領域)に重なる部分を描かない
    • 絵柄以外の透明部分(カットライン内で絵柄が無い余白領域)への塗りはそのまま残す
    • すなわち塗りの適用範囲は「カットライン領域 − 不透明領域」となる
  • ガイドを非表示にする
    • 重心(赤丸)・重心からの鉛直線(点線)・支持範囲(オレンジ線)を描かない
    • 転倒シミュレーションも本モードでは表示しない
  • 差込部のハイライトを非表示にする
    • 首部・ツメの青矩形を描かない
    • アクリル板本体・首部・ツメを統合した 1 本のカットライン絵柄のみが見える状態にする
  • 台座をフィギュアと同じ見た目にする
    • 台座の塗りつぶし色・線の色を、フィギュア(カットライン)と同じ色にそろえる (緑のハイライトにしない)

モードの切替はプレビュー内の表示操作コントロールから行えるようにしてください。


3Dプレビューモード

プレビューには、解析表示モード・完成プレビューモードに続く第3の表示モードとして、 完成品を立体で確認できる 3Dプレビューモードを用意してください。切替はプレビュー内の 表示操作コントロールから行い、有効時はプレビュー領域全体が3Dビューへ切り替わります。

本モードも完成プレビューモードと同じく表示のみの切替であり、解析結果・パラメータ・ エクスポートの内容には一切影響を与えません。パラメータ変更による再解析の結果は 3D表示にも即時反映されます(解析 → 状態更新 → 再描画のデータフローを共有する)。

技術・読み込み

  • Three.js + React Three Fiber で実装する
  • 3D関連モジュールは dynamic importReact.lazy 等)とし、3Dモードへ初めて 切り替えたときに読み込む。初期バンドル・2D利用時のロードには影響させない。 読み込み中はインジケータを表示する
  • 完全クライアントサイドの制約は3Dでも同じ(画像・テクスチャを外部へ送信しない)

シーン構成(実寸 mm 座標系)

シーンは実寸(mm)で構築する。原点は接地面上の台座 footprint 中心、Y軸は上が正、 Z軸は前(手前)が正とする(奥行軸の「正=前」の規約と一致させる)。

  • アクリル板(フィギュア本体)
    • 曲線補完済みカットライン(板本体・首部・ツメを統合した1本の輪郭)を 板厚で押し出したソリッドとする
    • 奥行方向の中心はスリット位置(台座奥行中心 + 差込口の前後オフセット)に置く
  • 印刷レイヤ(実物のUV印刷構造を再現する)
    • 実物は板の裏面に印刷され、奥(後ろ)から 白版 → 絵柄 → アクリル板 の レイヤ構造になる
    • 前から見ると絵柄がアクリル越しに見え、後ろから見ると白版(白い塗り)が 直接見える
    • 白版の範囲は絵柄(不透明領域)のみとする。カットラインまでの余白部分には 白版を敷かず、透明アクリルのままにする
    • 絵柄テクスチャは読み込み済みビットマップから生成する。白版マスクは α を しきい値(パラメータ「不透明領域のしきい値」と同じ判定)で2値化して作る
  • 台座
    • アクリル板と同じ板厚のアクリル板(「台座形状」で選んだ footprint。折れ線化した輪郭を 板厚で押し出す)を水平に置き、スリット(幅 = 差込口幅、開口 = 板厚、 位置 = 奥行中心 + 前後オフセット)を貫通穴として開ける
    • ツメはスリットへ差し込まれ、ツメ深さ = 板厚 = 台座の厚みなので ツメ底面は台座底面とツライチになる
    • 持ち上げ量ぶんの隙間は首部が埋める(2Dと同じ幾何)

マテリアル・環境

  • アクリルはリアル寄りの透明素材として表現する(MeshPhysicalMaterial の transmission 等。透明感・光沢・エッジの映り込み)。切断面(側面)も同素材とする
  • 台座もフィギュアと同じ透明アクリル素材とする
  • 環境はスタジオ風とする:床に接地影(ソフトシャドウ)を落とし、 ソフトな環境光で商品写真のように見せる。背景は単色

床(グリッド・テクスチャ)

  • 床には実寸(mm)のグリッド線を既定で表示する。マス目は 10mm、50mm ごとに強調線を 引き、原点(台座 footprint 中心)に揃える。グリッドは3Dビューポート内のトグルで オン/オフできる
  • 床には任意のテクスチャ画像を貼れる。切替は「なし(無地)/ 木目 / 画像…」で、 **既定はテクスチャなし(無地の床)**とする
    • 木目はサンプルとして同梱するテクスチャ(src/assets/textures/wood.png)。選んだ ときに初めて読み込む
    • ユーザーがアップロードした画像も床に貼れる。デコードはブラウザ内で完結させ、 外部へ送信しない(完全クライアントサイドの制約)
    • テクスチャは実寸で敷き詰める(タイル1枚 = 一定の mm 角)。画像はアスペクト比を保って タイルへ収める
    • 読み込みに失敗しても直前の床を保ち、クラッシュさせずにメッセージを表示する
  • テクスチャのアップロードUIは3Dビューポート上に置く。左のパラメータパネルは解析に 効く値だけを持ち、見た目の設定は持ち込まない

カメラ・操作

  • オービット操作(ドラッグ回転・ホイールズーム・パン)を提供する
  • 初期視点は正面やや斜め上から全体が収まる構図とする
  • 床より下へ回り込めないよう仰角を制限する
  • 視点リセット(初期構図へ戻す)ボタンを設ける

転倒シミュレーション(3D)

  • 「方向」と「傾き量」の2本のスライダーを設け、フィギュア全体(板 + 台座)を 台座の支持端を支点に、任意の方位へ連続的に傾けられるようにする
    • 方向スライダー:倒す向きの方位角(右 0°・前 90°・左 180°・後 270°。0〜360°)。 45°刻みの8方位と最悪方位(最小転倒角の向き)へスナップさせ、「最悪方位へ」の 一発操作も設ける(最も倒れやすい姿勢をすぐ確認できるようにする)
    • 傾き量スライダー:0° から、その方位の転倒角 + 余裕(10°程度)まで。0へ戻しやすくする
  • 支点は台座 footprint 凸包の支持直線(法線 = 倒す方向 d の直線で凸包を支えたときの 接触辺、接触が1点なら接線)とする。台座のバウンディングボックスの端から取ってはならない: 円・楕円では斜め方向の「bboxの角」が footprint の外側の点になり、台座が床から浮く
  • 転倒判定はその方位の転倒角(支持関数による θ(d))で行う。左右・前後を別々に判定すると、 斜め方向(最悪方位を含む)で倒れる姿勢を見落とす
  • 転倒角との関係を色で示す:転倒角の内側は安全色、転倒角を超えると警告色(赤)に 変えて「この角度で倒れる」ことを示す
  • ガイド(重心マーカー等)は通常表示しない。傾けているときのみ、支点(接触辺・接線)の ハイライトと転倒角までの余裕表示を出す(完成プレビューと同じ「ガイドを描かない」思想)

分解・差し込みアニメーション

  • 「分解 / 組立」ボタンを設け、押すたびにアクリル板が台座から上方へ滑らかに 持ち上がって分離降下してスリットへ差し込まれるアニメーションを再生する
  • 差込部(首部・ツメ)とスリットの対応関係を目視確認するための機能である
  • 傾けスライダーが 0 以外のときは傾きを 0 へ戻してから再生する(合成姿勢を作らない)

台座の半透明化(スリット断面確認)

  • 台座のみを強めの半透明にするトグルを設け、スリット内でのツメの収まり (位置・深さ・ツライチ)を透かして確認できるようにする

有効条件・他モードとの関係

  • 3Dモードへの切替は解析結果があるときのみ有効とする(未解析・解析エラー時は 切替ボタンを無効化する)
  • 3Dモード中は、2D前提のUI(ルーラー・2Dオーバーレイ・2D転倒シミュレーション・ 完成プレビューモードのトグル)を非表示または無効にする
  • 2Dへ戻したとき、2D側の表示状態(ズーム・パン等)は維持されていること

パフォーマンス・実装方針

  • レンダリングはオンデマンドとする(frameloop="demand" 等)。カメラ操作・ アニメーション・スライダー操作中のみ連続描画し、静止中はGPUを使わない
  • デバイスピクセル比は上限(2程度)でクランプする
  • テクスチャは長辺 2048px 程度を上限にダウンスケールする(3000px級の入力画像でも 快適に動作させる。解析用ピクセルデータとは独立に、描画用ビットマップから作る)
  • 3D用ジオメトリ(輪郭 → 押し出し形状、mm座標系への変換)の構築はReact非依存の 純粋ロジックとして実装し、解析結果が変わったときのみ再構築する
  • ImageData 等の巨大配列を React の state / props に載せない(既存の制約と同じ)

ルーラー

ビューワー(プレビュー)の**上端と左端にルーラー(目盛り)**を表示してください。

  • 上端:水平ルーラー(X 方向)/左端:垂直ルーラー(Y 方向)
  • 目盛りの単位は**実寸(mm)**とする
  • 原点(0, 0) は次の点にとる(画像の左上ではない)
    • X = 0:重心の X 座標(=重心からの鉛直線)
    • Y = 0:台座の底面(=接地面)
    • Y 軸は上を正とする(台座底面から上へ 0, 10, 20…。底面より下は負値)。 X 軸は右を正(重心より左は負値)
    • 解析結果が出るまでは原点が定まらないため、画像の左下を仮の原点としてよい
  • カットライン(絵柄+余白)の上端に来る目盛りは、指定した「フィギュア高さ」と一致しなければ ならない(「パラメータ」節「フィギュア高さ」を参照。ルーラーが実寸の検算になる)
  • ズーム・パンに追従し、表示中の範囲に対応する目盛りを描く
  • ズーム率に応じて目盛り間隔を自動で切り替える(例:1 / 5 / 10 / 50 / 100 mm)
    • 主目盛りには数値ラベルを付し、副目盛りは短い線のみとする
  • ルーラーはビューワー内容の上に固定表示し、パン・ズームの操作対象(ドラッグ・ホイール)を妨げないこと
  • ビューワーへ重ねる他の UI(解析中インジケータ等)とルーラーが重ならないように配置すること (「プレビュー」節「解析中インジケータの配置」を参照)

グリッド(2D)

2Dプレビューには実寸(mm)のグリッド線を敷けるようにし、ビューポート内のトグルで オン/オフできるようにしてください(既定は非表示。3Dの床グリッドが既定 ON なのとは異なり、 2Dでは絵柄・オーバーレイの読みやすさを優先する)。

本機能は表示のみの切替であり、解析結果・パラメータ・エクスポートには一切影響しません。

  • 格子はルーラーと同じ目盛り位置に引く(原点・間隔の自動切替はルーラーの規則に従うため、 格子とルーラーの目盛りは常に一致する)
  • 主目盛り・副目盛り・原点軸(0mm)で線の濃さを変え、原点(接地面・重心の鉛直線)が読み取れるようにする
  • グリッドはプレビュー内容(絵柄・オーバーレイ)の背面へ敷き、これらを覆い隠さないこと (透明PNGの抜けた部分に方眼紙のように透けて見える)
  • ズーム・パンに追従し、操作対象(ドラッグ・ホイール)を妨げないこと
  • 3Dモード中は表示しない(3Dは「床(グリッド・テクスチャ)」節のグリッドを持つ)

表示操作

以下を実装してください。

  • ホイールズーム
  • ドラッグパン
  • Fit表示
  • 100%表示
  • ルーラー表示(上端・左端)
  • グリッド表示の切替(実寸(mm)。「ルーラー」節「グリッド(2D)」を参照)
  • 完成プレビューモードの切替(「プレビュー」節を参照)
  • 3Dプレビューモードの切替(「3Dプレビューモード」節を参照)

結果表示

以下を一覧表示してください。

画像サイズ

実寸

重心座標(mm)

差込口中心

差込口幅

台座形状

台座幅

台座奥行

転倒角(左)

転倒角(右)

転倒角(前)

転倒角(後)

転倒角(最小・方位)

転倒角は台座の安定性そのものを表す指標です(大きいほど倒れにくく、0° は臨界)。 左右は台座幅、前後は台座奥行と差込口の前後オフセットで決まります。 転倒角(最小) は全方位で最も倒れやすい方向の値で、方位(方位角+8方位ラベル)を併記します (「転倒シミュレーション」節「最小転倒角」を参照)。台座幅・台座奥行の行には footprint の バウンディングボックス実寸を表示します(円形では直径×直径)。


エクスポート

SVGAdobe Illustrator ドキュメント(.ai) の 2 形式を出力してください。 いずれも以下を含み、実寸(mm)座標系で生成します。

  • 外形(アクリル板本体+首部+ツメを一体化したカットライン)
  • 差込口(首部・ツメ)
  • 台座
  • 台座に切るスリット(差込口)

台座は footprint のバウンディングボックス上辺を台座上面に合わせて描き、アクリル板本体が 台座と重なっていないこと・ツメが台座を貫通していないことを、出力形状の上でも保証してください。

台座のスリット

台座は「台座形状」で選んだ footprint の上面図として描き、その内側にスリットのパスを 描いてください。台座パーツだけを見て加工できるようにするためです。矩形以外の footprint (円・楕円・角丸・任意形状)の輪郭は、カットラインと同じく曲線コマンドで出力します (footprint のパス表現をそのまま写し、SVG と .ai で同一の幾何を共有する)。

  • スリットの幅 = 差込口幅(mm)(=ツメ幅)
  • スリットの開口(奥行方向)= 板厚(mm)
  • スリットの中心 = 台座の奥行中心 + 差込口の前後オフセット(正=前)
  • 台座 footprint の縦方向が奥行軸であり、上辺が後(奥)・下辺が前(手前) に対応する

「台座サイズの検査」を通っていれば、スリットは必ず台座の内側に収まります。

曲線コマンド

外形(カットライン)は折れ線ではなく、曲線補完後の形状を曲線コマンド(CQ 等)で出力してください。 複数パーツを包絡する場合・自己交差を除去した場合も、破綻のない外形として出力してください。

生成したファイルはブラウザからダウンロード可能としてください。図形の座標計算は 2 形式で 共有し、同じ解析結果からは同じ形状が出ることを保証してください。

絵柄画像の埋め込み

入稿用に「絵柄付きのアウトライン」が必要なため、元 PNG を線データと同じ実寸座標系へ 重ねて埋め込めるようにしてください。

  • .ai常に絵柄画像を含む。
  • SVG は線データのみを既定とし、ユーザーが選択したときだけ画像を埋め込む (画像を含めるとファイルが大きくなるため)。
  • 画像を含める場合は、画像の外接範囲も出力の表示領域に含める(見切れさせない)。

.ai 出力

.ai は Illustrator 9 以降 PDF 互換のコンテナであり、PDF として妥当なファイルを .ai 拡張子で保存すれば Illustrator が開いて編集できます。この性質を利用し、 PDF を生成して .ai として書き出す方式とします(サーバー不要・完全クライアントサイドの制約を守る)。

  • カットラインは編集可能なベジェパスとして出力する(ラスタ化しない)。
  • アートボードを実寸(mm)に一致させる。
  • Illustrator 上で扱いやすいよう、絵柄/カットライン/差込口・台座をレイヤーへ分離する (PDF の Optional Content Group を用いる)。

描画更新

パラメータ変更時は

解析
↓

状態更新

↓

再描画

を即時実行してください。

Reactの再レンダリングを活用し、不要な再計算は useMemouseCallback を利用して最小限に抑えてください。


状態管理

アプリ全体の状態はReact Hooksで管理してください。

過度なグローバル状態管理ライブラリ(Redux等)は使用しないでください。


エラーハンドリング

以下の場合はUI上へ分かりやすく表示してください。

  • PNG読み込み失敗
  • 非対応画像
  • 透明画像
  • 差込口が配置不可
  • 台座計算不可
  • 台座形状が利用できない(任意形状のソース未読込・読込失敗・輪郭抽出失敗。baseShapeFailed

例外によるクラッシュは禁止してください。

エラー表示の配置

エラーはプレビュー(ビューワー)の前面へオーバーレイで表示してください。プレビューの外 (列の上部など)へ積むと、エラーの表示・非表示のたびにビューワーの寸法が変わり、表示が跳ねます

  • 位置は解析中インジケータと同じ中央上部(上端ルーラーの帯より下)とし、目盛りを隠さない
  • オーバーレイはドラッグ&ドロップ・パン操作を妨げない(ポインタイベントを奪わない)ようにする
  • 画像未読み込みでもエラーは起こり得る(PNG読み込み失敗)ため、画像の有無に関わらず表示する

パフォーマンス

画像サイズが3000px程度でも快適に動作することを目標としてください。

大きな画像(3000px〜)を読み込んでもフリーズさせてはいけません。

以下を考慮してください。

  • 不要な再描画の抑制
  • メモ化
  • 必要最小限の画像解析
  • オーバーレイのみの再描画

大画像対策

フリーズの原因は画像の読み込み(デコード)ではなく、その後の重い解析処理をメインスレッドで同期実行していることです。

  • 読み込み(createImageBitmap → Canvas → getImageData)はブラウザ内部で最適化されており、ボトルネックではありません。
  • 実際に固まるのは、α プレーン抽出・二値化・外形抽出といった O(W×H) の全画素処理です。これらが同期的にメインスレッドを占有するため、その間 UI の描画・入力が停止します(α プレーン抽出は第1相、二値化以降は第2相。「アルファ閾値」節の実装上の注意を参照)。

したがって対策は「読み込みの高速化」ではなく、重い解析でメインスレッドを塞がないことを主眼とします。

第一の対策:解析の Web Worker 化(原寸維持)

重い第1相解析(全画素走査・輪郭抽出)を Web Worker 上で実行し、メインスレッドを解放してください。計算量そのものは減らさないため、解析精度は原寸のまま維持されます。

  • ImageDataArrayBuffer は Transferable。コピーせず Worker へ転送する。
  • 解析中は状態を analyzing とし、UI 上に解析中であることを表示する(進捗・スピナー等)。
  • パラメータのみの変更は軽量な第2相のみで済むため、原則メインスレッドで即時計算してよい(Worker を跨がない)。

補助的対策:解析解像度の上限(任意)

Web Worker 化してもなお解析が重い超巨大画像に限り、任意の補助手段として解析解像度の上限を設けてよいものとします(既定では無効でよい)。

  • 解析時のみ内部でダウンサンプリングし、mm_per_pixel は縮小後の解像度に合わせて算出する。
  • 表示・プレビュー・SVG エクスポートには元画像/原寸座標を用いる(結果は mm 実寸で保持するため整合する)。
  • 精度低下を伴うため、Web Worker 化の代替ではなく、あくまで最終手段とする。

その他

  • 解析はパラメータ変更のたびに全再計算せず、影響範囲に応じて再計算対象を絞る(第1相/第2相の分離を維持)。

将来拡張しやすい設計

以下の機能追加を考慮してください。

  • 複数差込口
  • 台座 footprint の穴あき対応(ドーナツ形状等)
  • 金属スタンド対応
  • アクリル以外の素材
  • 密度マップ
  • パーツ分割
  • 複数PNG同時計算

ロジックはUIと独立したモジュールとして設計してください。


コード品質

以下を遵守してください。

  • TypeScript Strict Mode
  • ESLint warningゼロ
  • Prettier準拠
  • any型禁止(やむを得ない場合を除く)
  • 型安全を重視する
  • 関数は単一責務とする
  • コメントは「なぜその処理が必要か」を説明する内容を中心とする
  • UI・画像処理・物理計算・描画を分離する

実装手順

以下の順番で実装してください。

  1. React + Vite環境構築
  2. UIレイアウト作成
  3. PNG読み込み
  4. スケール計算
  5. 重心計算
  6. プレビュー表示
  7. 差込口探索
  8. 台座サイズ計算
  9. 転倒シミュレーション
  10. オーバーレイ描画
  11. エクスポート(SVG / .ai)
  12. エラーハンドリング
  13. パフォーマンス最適化
  14. リファクタリング

設計方針

本アプリケーションはサーバーレスで動作する静的Webアプリケーションとして実装してください。

  • GitHub Pagesへそのままデプロイできること
  • 画像・解析データはブラウザ内のみで処理すること
  • 外部APIへデータを送信しないこと
  • 将来的にPWA化やWebAssemblyへの置き換えが容易な設計とすること