blockbench-plugins
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBlockbench Plugin Development
Blockbench 插件开发
Overview
概述
Blockbench runs on Electron (desktop) and as a web PWA, using THREE.js for 3D rendering and Vue 2 for reactive UI. Plugins have full access to global APIs within an isolated execution context.
Blockbench 运行于 Electron(桌面端)和网页 PWA 环境,使用 THREE.js 实现3D渲染,Vue 2 构建响应式UI。插件可在独立执行环境中完全访问全局API。
Quick Reference
快速参考
| Task | Approach |
|---|---|
| Create clickable command | |
| Show form/dialog | |
| Add sidebar panel | |
| Modify model elements | Use |
| Custom import/export | |
| React to changes | |
| 任务 | 实现方式 |
|---|---|
| 创建可点击命令 | |
| 显示表单/对话框 | |
| 添加侧边栏面板 | |
| 修改模型元素 | 使用 |
| 自定义导入/导出 | |
| 响应变更事件 | |
Plugin File Structure
插件文件结构
plugins/
└── my_plugin/
├── my_plugin.js # Main file (required, ID must match filename)
├── about.md # Extended docs (optional)
└── icon.png # 48x48 icon (optional)plugins/
└── my_plugin/
├── my_plugin.js # 主文件(必填,ID需与文件名匹配)
├── about.md # 扩展文档(可选)
└── icon.png # 48x48 图标(可选)Plugin Registration Template
插件注册模板
javascript
(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!');
}
});
})();javascript
(function() {
// 存储引用以便清理
let myAction, myPanel, myDialog, eventCallback;
Plugin.register('my_plugin', {
title: 'My Plugin',
author: 'Author Name',
description: 'Short description',
icon: 'extension', // Material图标名称
version: '1.0.0',
variant: 'both', // 'desktop'、'web' 或 'both'
min_version: '4.8.0',
tags: ['Utility'],
onload() {
// 在此初始化所有组件
},
onunload() {
// 关键操作:在此删除所有组件
},
oninstall() {
Blockbench.showQuickMessage('Installed!');
}
});
})();Actions
操作(Actions)
Actions are clickable commands for menus, toolbars, and keybindings.
javascript
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();Menu locations: , , , , , ,
'file''edit''transform''filter''tools''view''help'Action variants:
javascript
// 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 */ }
});Actions是用于菜单、工具栏和快捷键绑定的可点击命令。
javascript
myAction = new Action('my_action_id', {
name: 'Action Name',
description: 'Tooltip text',
icon: 'star',
category: 'edit', // 用于快捷键设置
condition: () => Cube.selected.length > 0,
keybind: new Keybind({ key: 'k', ctrl: true }),
click(event) {
// 操作逻辑
}
});
MenuBar.addAction(myAction, 'filter'); // 添加至“筛选”菜单
// 清理
myAction.delete();菜单位置: 、、、、、、
'file''edit''transform''filter''tools''view''help'Action变体:
javascript
// 切换按钮(开/关状态)
new Toggle('toggle_id', {
name: 'Feature',
default: false,
onChange(value) { /* 处理逻辑 */ }
});
// 工具(视口交互)
new Tool('tool_id', {
name: 'My Tool',
cursor: 'crosshair',
onCanvasClick(data) { /* 处理逻辑 */ },
onCanvasDrag(data) { /* 处理逻辑 */ }
});Dialogs
对话框(Dialogs)
javascript
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'] });javascript
myDialog = new Dialog({
id: 'my_dialog',
title: 'Dialog Title',
width: 540,
form: {
name: { label: '名称', type: 'text', value: 'default' },
count: { label: '数量', type: 'number', value: 10, min: 1, max: 100 },
enabled: { label: '启用', type: 'checkbox', value: true },
mode: {
label: '模式',
type: 'select',
options: { a: '选项A', b: '选项B' },
value: 'a'
},
color: { label: '颜色', type: 'color', value: '#ff0000' },
// 条件字段
advanced: {
label: '高级设置',
type: 'text',
condition: (form) => form.enabled
}
},
onConfirm(formData) {
console.log(formData.name, formData.count);
this.hide();
}
});
myDialog.show();
// 快速对话框
Blockbench.textPrompt('输入值', 'default', (text) => { });
Blockbench.showMessageBox({ title: '提示', message: '文本内容', buttons: ['确定'] });Panels
面板(Panels)
Panels appear in sidebars with Vue components.
javascript
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();面板以Vue组件形式展示在侧边栏中。
javascript
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">刷新</button>
</div>
`,
data() {
return { title: '项目列表', items: [] };
},
methods: {
refresh() {
this.items = Cube.selected.map(c => ({ name: c.name }));
}
}
}
});
// 清理
myPanel.delete();Model Manipulation (with Undo)
模型操作(带撤销功能)
CRITICAL: Always wrap modifications in Undo for user reversibility.
javascript
// 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中,确保用户可撤销。
javascript
// 开始追踪
Undo.initEdit({ elements: Cube.selected });
// 修改元素
Cube.selected.forEach(cube => {
cube.from[0] += 5;
cube.to[1] = 20;
cube.rotation[1] = 45;
});
// 更新视图
Canvas.updateView({
elements: Cube.selected,
element_aspects: { geometry: true, transform: true }
});
// 提交修改
Undo.finishEdit('移动立方体');Creating Elements
创建元素
javascript
// 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 undojavascript
// 立方体
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]); // 添加至组
// 组(骨骼)
let group = new Group({
name: 'bone_arm',
origin: [0, 12, 0]
}).init();
group.addTo(); // 添加至根节点
// 纹理
let texture = new Texture({ name: 'my_texture' });
texture.fromPath('/path/to/file.png'); // 或使用.fromDataURL()
texture.add(true); // true = 添加至撤销栈Global Collections
全局集合
| Collection | Description |
|---|---|
| All cubes / selected cubes |
| All groups / selected groups |
| All meshes / selected meshes |
| All textures / selected |
| All animations / selected |
| All outliner elements |
| 集合 | 描述 |
|---|---|
| 所有立方体 / 选中的立方体 |
| 所有组 / 选中的组 |
| 所有网格 / 选中的网格 |
| 所有纹理 / 选中的纹理 |
| 所有动画 / 选中的动画 |
| 所有大纲元素 |
Event System
事件系统
javascript
// Subscribe
eventCallback = (data) => { /* handle */ };
Blockbench.on('update_selection', eventCallback);
// Unsubscribe (use SAME function reference)
Blockbench.removeListener('update_selection', eventCallback);Common events: , , , , , , , , , , , , , ,
update_selectionselect_projectnew_projectload_projectsave_projectclose_projectadd_cubeadd_groupadd_textureadd_animationselect_animationrender_frameundoredofinish_editSee for full list.
references/events.mdjavascript
// 订阅事件
eventCallback = (data) => { /* 处理逻辑 */ };
Blockbench.on('update_selection', eventCallback);
// 取消订阅(需使用相同的函数引用)
Blockbench.removeListener('update_selection', eventCallback);常见事件: 、、、、、、、、、、、、、、
update_selectionselect_projectnew_projectload_projectsave_projectclose_projectadd_cubeadd_groupadd_textureadd_animationselect_animationrender_frameundoredofinish_edit完整事件列表请查看 。
references/events.mdCustom Menus
自定义菜单
javascript
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 positionjavascript
let menu = new Menu([
'existing_action_id',
myAction,
'_', // 分隔符
{
name: '自定义项',
icon: 'star',
click() { /* 处理逻辑 */ }
},
{
name: '子菜单',
children: [ /* 更多项 */ ]
}
]);
menu.open(event); // 在鼠标位置打开菜单Format and Codec (Import/Export)
格式与编解码器(导入/导出)
javascript
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();javascript
const myCodec = new Codec('my_codec', {
name: 'My Format',
extension: 'mymodel',
compile(options) {
// 模型 → 文件内容
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) {
// 文件内容 → 模型
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
});
// 清理
myFormat.delete();
myCodec.delete();Condition Patterns
条件模式
javascript
// 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()
}javascript
// 函数形式
condition: () => Format.id === 'bedrock' && Cube.selected.length > 0
// 对象形式(逻辑与组合)
condition: {
formats: ['bedrock', 'java_block'],
modes: ['edit', 'paint'],
project: true,
selected: { cube: 1 },
method: () => customCheck()
}Code Style Conventions
代码风格规范
| Type | Convention | Examples |
|---|---|---|
| Classes | PascalCase | |
| Methods | camelCase | |
| Properties | snake_case | |
| Event/Action IDs | snake_case | |
| 类型 | 规范 | 示例 |
|---|---|---|
| 类 | PascalCase | |
| 方法 | camelCase | |
| 属性 | snake_case | |
| 事件/Action ID | snake_case | |
Critical Cleanup Pattern
关键清理模式
MUST delete all components in to prevent memory leaks.
onunload()javascript
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();
}
});必须在中删除所有组件,防止内存泄漏。
onunload()javascript
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();
}
});Anti-Patterns
反模式
javascript
// ❌ 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 = {};javascript
// ❌ 未存储引用 = 内存泄漏
onload() { new Action('leaked', { }); }
// ❌ 未使用Undo = 用户无法撤销
Cube.selected.forEach(c => c.from[0] += 1);
// ❌ 打包THREE.js(已全局可用)
import * as THREE from 'three';
// ❌ 污染全局作用域
window.myData = {};Built-in Libraries (Do Not Bundle)
内置库(请勿打包)
- THREE — Three.js 3D rendering
- Vue — Vue 2 reactive UI
- JSZip — ZIP handling
- marked — Markdown parsing
- Molang — Molang expression parser
- THREE — Three.js 3D渲染库
- Vue — Vue 2响应式UI库
- JSZip — ZIP文件处理库
- marked — Markdown解析库
- Molang — Molang表达式解析器
Icons
图标
javascript
icon: 'star' // Material icon
icon: 'fa-bone' // Font Awesome
icon: 'icon-player' // Blockbench custom
icon: 'data:image/...' // Base64 imagejavascript
icon: 'star' // Material图标
icon: 'fa-bone' // Font Awesome图标
icon: 'icon-player' // Blockbench自定义图标
icon: 'data:image/...' // Base64图片TypeScript Setup
TypeScript配置
bash
npm i --save-dev blockbench-typestypescript
/// <reference types="blockbench-types" />bash
npm i --save-dev blockbench-typestypescript
/// <reference types="blockbench-types" />Template Files
模板文件
- — Complete starter plugin
assets/plugin_template.js - — Custom format/codec example
assets/format_plugin.js
- — 完整的插件启动模板
assets/plugin_template.js - — 自定义格式/编解码器示例
assets/format_plugin.js
References
参考文档
- — Full event list
references/events.md - — Detailed API reference
references/api.md - — Element types and properties
references/elements.md
- — 完整事件列表
references/events.md - — 详细API参考
references/api.md - — 元素类型与属性说明
references/elements.md