エクステンションは、iframeベースのプラグインシステムで、drawtonomyに機能を追加できます。
@drawtonomy/sdkと@drawtonomy/dev-serverを使ってエクステンションの開発・テストが可能です。
pnpm add -g @drawtonomy/dev-server
drawtonomy-dev-server
# → http://localhost:3000drawtonomy.comと同じアプリをローカルでダウンロード・配信します。
cd extensions/ai-scene-generator
pnpm install
pnpm dev
# → http://localhost:3001http://localhost:3000/?ext=http://localhost:3001/manifest.json
右側にAI Scene GeneratorのUIを持つエクステンションパネルが表示されます。
- APIプロバイダーを選択(AnthropicまたはOpenAI)
- モデルを選択(例: Claude Sonnet 4, GPT-4o)
- APIキーを入力
- シーンを記述(例:
A two-lane road with two cars and a pedestrian) - "Generate Scene"をクリック
エクステンションは?ext=<manifestUrl>URLパラメータで読み込まれます。
# ローカルエクステンション(dev-server使用)
http://localhost:3000/?ext=http://localhost:3001/manifest.json
# デプロイ済みエクステンション(drawtonomy.com使用)
https://drawtonomy.com?ext=https://my-extension.vercel.app/manifest.json
# 複数エクステンション
http://localhost:3000/?ext=http://localhost:3001/manifest.json&ext=http://localhost:3002/manifest.json
注意:
drawtonomy.com(HTTPS)はブラウザのPrivate Network Access制限により、localhost(HTTP)からエクステンションを読み込めません。ローカル開発には@drawtonomy/dev-serverを使用するか、エクステンションをHTTPSホストにデプロイしてください。
my-extension/
manifest.json # エクステンション定義(必須)
index.html # エントリーポイント(必須)
src/ # ソースコード
mkdir my-extension && cd my-extension
pnpm init
pnpm add @drawtonomy/sdk{
"id": "my-extension",
"name": "My Extension",
"version": "1.0.0",
"description": "What this extension does",
"author": { "name": "Your Name" },
"entry": "./index.html",
"capabilities": ["shapes:write", "shapes:read", "ui:panel"]
}エクステンションはiframe内で実行されます。任意のフレームワーク(React, Vue, Svelte, vanilla JS等)を使用できます。
SDK使用(推奨):
import { ExtensionClient, createVehicle } from '@drawtonomy/sdk'
const client = new ExtensionClient('my-extension')
const init = await client.waitForInit()
console.log('Connected! Capabilities:', init.grantedCapabilities)
// ボタンクリックで車両を追加
document.getElementById('addBtn')!.addEventListener('click', () => {
client.addShapes([createVehicle(200, 200, { templateId: 'sedan' })])
})vanilla JS(SDKなし):
<!DOCTYPE html>
<html>
<head><title>My Extension</title></head>
<body>
<button id="addBtn">Add Vehicle</button>
<script>
// 1. ホストにready信号を送信
window.parent.postMessage({ type: 'ext:ready', payload: { manifestId: 'my-extension' } }, '*')
// 2. ホストからのinitメッセージを待機
window.addEventListener('message', (event) => {
if (event.data.type === 'ext:init') {
console.log('Connected! Capabilities:', event.data.payload.grantedCapabilities)
}
})
// 3. ボタンクリックでシェイプを追加
document.getElementById('addBtn').addEventListener('click', () => {
window.parent.postMessage({
type: 'ext:shapes-add',
payload: {
shapes: [{
id: 'my-vehicle-1',
type: 'vehicle',
x: 200, y: 200,
rotation: 0, zIndex: 0,
props: {
w: 90, h: 45,
color: 'black', size: 'm',
attributes: { type: 'vehicle', subtype: 'car' },
osmId: '', templateId: 'sedan'
}
}]
}
}, '*')
})
</script>
</body>
</html># npx serveを使用
npx serve . --port 3001
# Viteを使用
pnpm dev --port 3001# ターミナル1: drawtonomy dev server
drawtonomy-dev-server
# ターミナル2: エクステンション
pnpm dev --port 3001
# ブラウザ
open "http://localhost:3000/?ext=http://localhost:3001/manifest.json"マニフェストはエクステンションのメタデータと必要な権限を定義します。
| フィールド | 必須 | 説明 |
|---|---|---|
id |
Yes | 一意のID(小文字英数字+ハイフン) |
name |
Yes | 表示名 |
version |
Yes | Semver形式(例: 1.0.0) |
description |
Yes | 説明 |
author |
Yes | { name: string, url?: string } |
entry |
Yes | エントリーHTMLへの相対パス |
icon |
- | アイコンへの相対パス |
capabilities |
Yes | 必要な権限の配列 |
minHostVersion |
- | 最小ホストバージョン |
エクステンションはマニフェストで宣言したケイパビリティのみ使用できます。宣言されていないケイパビリティへのメッセージはホストによって拒否されます。
| ケイパビリティ | 説明 | ユースケース |
|---|---|---|
shapes:write |
シェイプの追加・更新・削除 | AI生成、インポート、テンプレート |
shapes:read |
既存シェイプの読み取り | エクスポート、分析 |
snapshot:read |
フルスナップショットの取得 | バックアップ、変換 |
snapshot:export |
シーンのエクスポート(SVG/PNG/JPEG/PDF/EPS) | 動画生成、スクリーンショット |
viewport:read |
ビューポート情報の取得 | 位置を考慮した配置 |
selection:read |
選択状態の読み取り | 選択中のシェイプを処理 |
ui:panel |
サイドパネルにiframe UIを表示 | カスタムUI |
ui:notify |
ホストにトースト通知を表示 | 完了通知 |
エクステンションはwindow.parent.postMessage()を介してホストと通信します。
ヒント:
@drawtonomy/sdkのExtensionClientを使用すると、postMessageを直接扱う必要がなくなります。
| メッセージ | ケイパビリティ | 説明 |
|---|---|---|
ext:ready |
(なし) | 接続確立。ホストがext:initで応答 |
ext:shapes-add |
shapes:write |
シェイプを追加 |
ext:shapes-update |
shapes:write |
シェイプのプロパティを更新 |
ext:shapes-delete |
shapes:write |
シェイプを削除 |
ext:shapes-request |
shapes:read |
シェイプデータを要求(フィルター付き) |
ext:snapshot-request |
snapshot:read |
スナップショットを要求 |
ext:export-request |
snapshot:export |
シーンをエクスポート(format: svg/png/jpeg/pdf/eps)。returnData: trueでBase64 data URIとしてデータを取得可能 |
ext:viewport-request |
viewport:read |
ビューポート情報を要求 |
ext:selection-request |
selection:read |
選択状態を要求 |
ext:notify |
ui:notify |
トースト通知を表示 |
ext:resize |
ui:panel |
iframeをリサイズ |
| メッセージ | タイミング |
|---|---|
ext:init |
ready後、ケイパビリティ/ビューポート情報を送信 |
ext:shapes-response |
shapes-requestへの応答 |
ext:snapshot-response |
snapshot-requestへの応答 |
ext:export-response |
export-requestへの応答。returnData: trueの場合dataにBase64 data URIが含まれる。それ以外はホスト側でダウンロード実行 |
ext:viewport-response |
viewport-requestへの応答 |
ext:selection-response |
selection-requestへの応答 |
ext:error |
エラー発生時 |
@drawtonomy/sdkパッケージはエクステンション開発用のヘルパーを提供します。
postMessage通信をラップする高レベルAPI:
import { ExtensionClient } from '@drawtonomy/sdk'
const client = new ExtensionClient('my-extension')
// 初期化を待機
const init = await client.waitForInit()
console.log('Capabilities:', init.grantedCapabilities)
// シェイプを追加
client.addShapes([...])
// シェイプを読み取り(Promiseを返す)
const shapes = await client.requestShapes({ types: ['vehicle'] })
// スナップショットを取得
const snapshot = await client.requestSnapshot()
// シーンをBase64 data URIとしてエクスポート(snapshot:exportケイパビリティが必要)
const result = await client.requestExport('png', { returnData: true })
// result.data = "data:image/png;base64,iVBOR..."
// result.mimeType = "image/png"
// result.filename = "scene-2026-04-02T12-00-00.png"
// エクスポートしてファイルダウンロード(デフォルト動作)
await client.requestExport('svg')
// ビューポートを取得
const viewport = await client.requestViewport()
// 選択状態を取得
const selection = await client.requestSelection()
// 通知を送信
client.notify('Done!', 'success')シェイプ作成用のヘルパー:
import {
createVehicle,
createPedestrian,
createLaneWithBoundaries,
createRectangle,
createText,
} from '@drawtonomy/sdk'
// 車両を作成
const car = createVehicle(200, 300, { templateId: 'sedan', color: 'blue' })
// 境界付きレーンを作成(point → linestring → laneの依存関係を処理)
const laneShapes = createLaneWithBoundaries(
[{ x: 0, y: 0 }, { x: 500, y: 0 }], // 左境界の点
[{ x: 0, y: 70 }, { x: 500, y: 70 }], // 右境界の点
{ laneOptions: { color: 'default' } }
)
// ホストに送信
client.addShapes([...laneShapes, car])| タイプ | 説明 | 主要プロパティ |
|---|---|---|
point |
座標点 | color, visible |
linestring |
線(境界) | pointIds[], color, strokeWidth |
lane |
道路レーン | leftBoundaryId, rightBoundaryId, color |
vehicle |
車両 | w, h, templateId (default, sedan, bus, truck, motorcycle, bicycle) |
pedestrian |
歩行者 | w, h, templateId (filled, walking, simple) |
rectangle |
矩形 | w, h, color, fill |
ellipse |
楕円 | w, h, color, fill |
arrow |
矢印 | w, h, direction |
text |
テキスト | text, fontSize, font |
polygon |
ポリゴン | pointIds[], color, fillOpacity |
traffic_light |
信号機 | w, h |
crosswalk |
横断歩道 | pointIds[], color |
freehand |
フリーハンド | points[], color |
image |
画像 | w, h, src |
エクステンションを公開するには、manifest.jsonとビルド出力をHTTPSでホスティングします。
CORSヘッダーがデフォルトで含まれているため、追加設定は不要です。
https://drawtonomy.com?ext=https://username.github.io/my-extension/manifest.json
vercel.jsonにCORSヘッダーを追加:
{
"headers": [
{
"source": "/manifest.json",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}_headersファイルを作成:
/manifest.json
Access-Control-Allow-Origin: *
ローカル開発には@drawtonomy/dev-serverを使用:
# ターミナル1
drawtonomy-dev-server
# ターミナル2
pnpm dev --port 3001
# ブラウザ
open "http://localhost:3000/?ext=http://localhost:3001/manifest.json"