blockbench-plugins について
BlockbenchのAPIを使ったプラグイン開発を支援するスキルです。UI、モデル編集、イベント、インポート/エクスポート、後処理、バージョンに応じた検証を案内します。
主な機能
- 01アクション・ダイアログ・サイドパネル
- 02Undo対応のモデル編集
- 03イベント購読と後処理
- 04カスタムインポート/エクスポート用Codec
- 05ホストのバージョンとAPIの確認
活用シーン
- 01メニューやツールバーへのコマンド追加
- 02プラグイン用サイドパネルの作成
- 03モデル形式のインポートまたはエクスポート実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jasonjgardner/ blockbench-mcp-project --skill blockbench-pluginsインストール後、Claude Code で/blockbench-pluginsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/format_plugin.jsplugin_template.jsreferences/api.mdelements.mdevents.md詳細
Blockbench プラグイン開発
概要
Blockbench はデスクトップとウェブで動作し、ホスト API と同梱のレンダリング/UI ライブラリをプラグインに公開します。API を選ぶ前に、対象となるホストのバージョンと型を確認してください。プラグイン変数が分離されていても、ネイティブモジュールやファイルシステムへ無制限にアクセスできるわけではありません。古い Wiki の例とホストの動作が異なる場合は、最新の生成された API リファレンスと対象のチェックアウトを優先してください。実際に検証した環境に合わせて、variant と min_version を設定してください。
モデル形式の選択、UV 単位、リグの違い、エクスポート時の受け渡しについては、形式と受け渡しのガイダンスを参照してください。形式の機能フラグはホストの動作を示すものであり、カスタムコーデックに不足している検証や実行時のシリアライズを実装するものではありません。
クイックリファレンス
| タスク | 方法 |
|---|---|
| クリック可能なコマンドを作成する | new Action() - メニュー/ツールバーに追加 |
| フォーム/ダイアログを表示する | フォームフィールドを指定して new Dialog() を使用 |
| サイドバーパネルを追加する | Vue コンポーネントを使用して new Panel() を作成 |
| モデル要素を変更する | Undo.initEdit() → 変更 → Undo.finishEdit() を使用 |
| カスタムのインポート/エクスポート | new Codec() + new ModelFormat() |
| 変更に応答する | Blockbench.on('event_name', callback) |
プラグインのファイル構成
plugins/
└── my_plugin/
├── my_plugin.js # Main file (required, ID must match filename)
├── about.md # Extended docs (optional)
└── icon.png # 48x48 icon (optional)
プラグイン登録テンプレート
(function() {
// Store references for cleanup
let myAction, myPanel, myDialog, eventCallback;
Plugin.register('my_plugin', {
title: 'My Plugin',
author: 'Author Name',
description: 'Short description',
icon: 'extension', // Material icon name
version: '1.0.0',
variant: 'both', // 'desktop', 'web', or 'both'
min_version: '4.8.0',
tags: ['Utility'],
onload() {
// Initialize all components here
},
onunload() {
// CRITICAL: Delete ALL components here
},
oninstall() {
Blockbench.showQuickMessage('Installed!');
}
});
})();
アクション
アクションは、メニュー、ツールバー、キー割り当てから実行できるクリック可能なコマンドです。
myAction = new Action('my_action_id', {
name: 'Action Name',
description: 'Tooltip text',
icon: 'star',
category: 'edit', // For keybind settings
condition: () => Cube.selected.length > 0,
keybind: new Keybind({ key: 'k', ctrl: true }),
click(event) {
// Action logic
}
});
MenuBar.addAction(myAction, 'filter'); // Add to Filter menu
// Cleanup
myAction.delete();
メニューの配置先: 'file'、'edit'、'transform'、'filter'、'tools'、'view'、'help'
アクションのバリエーション:
// Toggle (on/off state)
new Toggle('toggle_id', {
name: 'Feature',
default: false,
onChange(value) { /* handle */ }
});
// Tool (viewport interaction)
new Tool('tool_id', {
name: 'My Tool',
cursor: 'crosshair',
onCanvasClick(data) { /* handle */ },
onCanvasDrag(data) { /* handle */ }
});
ダイアログ
myDialog = new Dialog({
id: 'my_dialog',
title: 'Dialog Title',
width: 540,
form: {
name: { label: 'Name', type: 'text', value: 'default' },
count: { label: 'Count', type: 'number', value: 10, min: 1, max: 100 },
enabled: { label: 'Enabled', type: 'checkbox', value: true },
mode: {
label: 'Mode',
type: 'select',
options: { a: 'Option A', b: 'Option B' },
value: 'a'
},
color: { label: 'Color', type: 'color', value: '#ff0000' },
// Conditional field
advanced: {
label: 'Advanced',
type: 'text',
condition: (form) => form.enabled
}
},
onConfirm(formData) {
console.log(formData.name, formData.count);
this.hide();
}
});
myDialog.show();
// Quick dialogs
Blockbench.textPrompt('Enter Value', 'default', (text) => { });
Blockbench.showMessageBox({ title: 'Alert', message: 'Text', buttons: ['OK'] });
パネル
パネルは Vue コンポーネントを使用してサイドバーに表示されます。
myPanel = new Panel('my_panel', {
name: 'My Panel',
icon: 'dashboard',
condition: () => Format.animation_mode,
default_position: {
slot: 'left_bar', // 'left_bar', 'right_bar', 'bottom'
height: 300
},
component: {
template: `
<div>
<h3>{{ title }}</h3>
<ul><li v-for="item in items">{{ item.name }}</li></ul>
<button @click="refresh">Refresh</button>
</div>
`,
data() {
return { title: 'Items', items: [] };
},
methods: {
refresh() {
this.items = Cube.selected.map(c => ({ name: c.name }));
}
}
}
});
// Cleanup
myPanel.delete();
モデルの操作(Undo 対応)
永続的なプロジェクトの編集は、影響を受けるすべての要素を含め、ネイティブの Undo に登録してください。トランザクションを開始する前に対象を検証し、非同期の入力を準備してください。失敗した場合は、トランザクションをキャンセルするか、状態を復元してください。ネイティブアクションやペイント API が独自のトランザクションを管理する場合は、競合するネストしたトランザクションを追加しないでください。読み取り専用の確認、カメラの移動、ファイルへの書き込みは、Undo 対応のモデル変更とは異なります。Undo のドキュメントを参照してください。
// Start tracking
Undo.initEdit({ elements: Cube.selected });
// Modify elements
Cube.selected.forEach(cube => {
cube.from[0] += 5;
cube.to[1] = 20;
cube.rotation[1] = 45;
});
// Update view
Canvas.updateView({
elements: Cube.selected,
element_aspects: { geometry: true, transform: true }
});
// Commit
Undo.finishEdit('Move cubes');
要素の作成
作成した新しいオブジェクトは、空の初期リストだけでなく、トランザクションの完了時に記録する必要があります。
Undo.initEdit({ elements: [], outliner: true });
const createdCube = new Cube({ name: 'body_geo', from: [-4, 0, -2], to: [4, 12, 2] }).init();
Undo.finishEdit('Add body cube', { elements: [createdCube], outliner: true });
Canvas.updateAll();
以下のスニペットは、ホストの個別のコンストラクターの使用例です。既存のプロジェクトを変更する場合は、適切なトランザクション内に配置してください。グループの原点はピボットです。親子関係を設定しても、その原点がキューブの境界に移動量として加算されることはありません。
// Cube
let cube = new Cube({
name: 'my_cube',
from: [0, 0, 0],
to: [16, 16, 16],
origin: [8, 8, 8],
rotation: [0, 45, 0]
}).init();
cube.addTo(Group.selected[0]); // Add to group
// Group (bone)
let group = new Group({
name: 'bone_arm',
origin: [0, 12, 0]
}).init();
group.addTo(); // Add to root
// Texture
let texture = new Texture({ name: 'my_texture' });
texture.fromPath('/path/to/file.png'); // or .fromDataURL()
texture.add(true); // true = add to undo
グローバルコレクション
| コレクション | 説明 |
|---|---|
Cube.all / Cube.selected | すべてのキューブ/選択中のキューブ |
Group.all / Group.selected | すべてのグループ/選択中のグループ |
Mesh.all / Mesh.selected | すべてのメッシュ/選択中のメッシュ |
Texture.all / Texture.selected | すべてのテクスチャ/選択中のテクスチャ |
Animation.all / Animation.selected | すべてのアニメーション/選択中のアニメーション |
Outliner.elements | アウトライナーのすべての要素 |
イベントシステム
// Subscribe
eventCallback = (data) => { /* handle */ };
Blockbench.on('update_selection', eventCallback);
// Unsubscribe (use SAME function reference)
Blockbench.removeListener('update_selection', eventCallback);
一般的なイベント: update_selection、select_project、new_project、load_project、save_project、close_project、add_cube、add_group、add_texture、add_animation、select_animation、render_frame、undo、redo、finish_edit
一覧については references/events.md を参照してください。
カスタムメニュー
let menu = new Menu([
'existing_action_id',
myAction,
'_', // Separator
{
name: 'Custom Item',
icon: 'star',
click() { /* handle */ }
},
{
name: 'Submenu',
children: [ /* more items */ ]
}
]);
menu.open(event); // Open at mouse position
形式とコーデック(インポート/エクスポート)
const myCodec = new Codec('my_codec', {
name: 'My Format',
extension: 'mymodel',
compile(options) {
// Model → file content
let data = { bones: [] };
Group.all.forEach(g => {
data.bones.push({
name: g.name,
pivot: g.origin,
cubes: g.children.filter(c => c instanceof Cube).map(c => ({
from: c.from, to: c.to
}))
});
});
return JSON.stringify(data, null, 2);
},
parse(content, path) {
// File content → model
let data = JSON.parse(content);
newProject(myFormat);
data.bones.forEach(b => {
let group = new Group({ name: b.name, origin: b.pivot }).init();
b.cubes.forEach(c => {
new Cube({ from: c.from, to: c.to }).init().addTo(group);
});
});
Canvas.updateAll();
}
});
const myFormat = new ModelFormat('my_format', {
id: 'my_format',
name: 'My Format',
icon: 'icon_name',
codec: myCodec,
box_uv: true,
bone_rig: true,
animation_mode: true
});
// Cleanup
myFormat.delete();
myCodec.delete();
条件パターン
// Function
condition: () => Format.id === 'bedrock' && Cube.selected.length > 0
// Object (combined with AND)
condition: {
formats: ['bedrock', 'java_block'],
modes: ['edit', 'paint'],
project: true,
selected: { cube: 1 },
method: () => customCheck()
}
コードスタイルの規約
| 種類 | 規約 | 例 |
|---|---|---|
| クラス | PascalCase | OutlinerElement、Animation |
| メソッド | camelCase | updateTransform、getSelectedFaces |
| プロパティ | snake_case | data_points、uv_offset |
| イベント/アクション ID | snake_case | select_project、my_action |
重要なクリーンアップパターン
メモリリークを防ぐため、onunload() ですべてのコンポーネントを必ず削除してください。
let action, panel, dialog, toolbar, format, codec, eventCallback, css;
Plugin.register('my_plugin', {
onload() {
action = new Action('id', { /* ... */ });
panel = new Panel('id', { /* ... */ });
format = new ModelFormat('id', { /* ... */ });
codec = new Codec('id', { /* ... */ });
eventCallback = (data) => { };
Blockbench.on('update_selection', eventCallback);
css = Blockbench.addCSS('.my-class { color: blue; }');
},
onunload() {
action.delete();
panel.delete();
format.delete();
codec.delete();
Blockbench.removeListener('update_selection', eventCallback);
css.delete();
}
});
アンチパターン
// ❌ No stored reference = memory leak
onload() { new Action('leaked', { }); }
// ❌ No Undo = users can't reverse
Cube.selected.forEach(c => c.from[0] += 1);
// ❌ Bundling THREE.js (already global)
import * as THREE from 'three';
// ❌ Global pollution
window.myData = {};
組み込みライブラリ(バンドルしないでください)
- THREE — Three.js の 3D レンダリング
- Vue — ホストが提供するリアクティブ UI。ホストがサポートするコンポーネント API に合わせてください
- JSZip — ZIP の処理
- marked — Markdown の解析
- Molang — Molang 式パーサー
アイコン
icon: 'star' // Material icon
icon: 'fa-bone' // Font Awesome
icon: 'icon-player' // Blockbench custom
icon: 'data:image/...' // Base64 image
TypeScript のセットアップ
npm i --save-dev blockbench-types
/// <reference types="blockbench-types" />
テンプレートファイル
assets/plugin_template.js— プラグインの完全なスターターassets/format_plugin.js— カスタム形式/コーデックの例
参考資料
- references/events.md — イベントのリファレンス。対象ホストでバージョン固有のイベントを確認してください
- references/api.md — ローカル API の例
- references/elements.md — 要素の種類とプロパティ
- プラグインの作成 — 登録、ライフサイクル、開発に関する公式ガイダンス
- 生成された API リファレンス — 現在のホストの型とコメント
代表的なプロジェクトを使ってプラグインの編集内容を検証してください。期待されるデータ/プレビュー、元に戻す操作の後にやり直す操作を行った結果、アンロード/再読み込み時のクリーンアップを確認します。形式/コーデックでは、代表的なエクスポートと再インポートを確認してください。サポートされている場合は、UV 寸法、階層、名前、テクスチャ参照、アニメーションの動作も確認します。コンパイルが成功しても、ネイティブのエディター状態やシリアライズされた出力は検証されません。