blockymodel-web 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/dist/sample.blockymodel +133 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# blockymodel-web
|
|
2
|
+
|
|
3
|
+
Web-based 3D editor for BlockyModel format (.blockymodel) built with [Three.js](https://threejs.org/).
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
**[Live Demo](https://editor.hytalegarage.com)** - Try the editor in your browser
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **3D Viewport** - Interactive scene with orbit controls
|
|
13
|
+
- **Selection** - Click to select objects with visual highlighting
|
|
14
|
+
- **Transform Gizmos** - Move, rotate, and scale with keyboard shortcuts (G/R/S)
|
|
15
|
+
- **Property Panel** - Edit position, rotation, scale, and shape properties
|
|
16
|
+
- **Hierarchy Panel** - Tree view with add/delete/duplicate via context menu
|
|
17
|
+
- **UV Editor** - Per-face texture offset, mirror, and rotation
|
|
18
|
+
- **Undo/Redo** - Full history with Ctrl+Z / Ctrl+Y
|
|
19
|
+
- **Save/Export** - Download as .blockymodel JSON
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install blockymodel-web three
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### As a Library
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import {
|
|
33
|
+
Editor,
|
|
34
|
+
ViewerController,
|
|
35
|
+
BlockyModelLoader,
|
|
36
|
+
PropertyPanel,
|
|
37
|
+
HierarchyPanel
|
|
38
|
+
} from 'blockymodel-web';
|
|
39
|
+
|
|
40
|
+
// Create container
|
|
41
|
+
const container = document.getElementById('viewport');
|
|
42
|
+
|
|
43
|
+
// Initialize viewer
|
|
44
|
+
const viewer = new ViewerController(container);
|
|
45
|
+
|
|
46
|
+
// Load a model
|
|
47
|
+
const loader = new BlockyModelLoader();
|
|
48
|
+
const model = await loader.loadAsync('/path/to/model.blockymodel');
|
|
49
|
+
viewer.scene.add(model);
|
|
50
|
+
|
|
51
|
+
// Enable editing
|
|
52
|
+
const editor = new Editor(
|
|
53
|
+
viewer.scene,
|
|
54
|
+
viewer.camera,
|
|
55
|
+
viewer.renderer,
|
|
56
|
+
viewer.controls
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Add UI panels
|
|
60
|
+
const hierarchy = new HierarchyPanel(
|
|
61
|
+
document.getElementById('hierarchy'),
|
|
62
|
+
editor
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const properties = new PropertyPanel(
|
|
66
|
+
document.getElementById('properties'),
|
|
67
|
+
editor
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Standalone App
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/ZacxDev/blockymodel-web.git
|
|
75
|
+
cd blockymodel-web
|
|
76
|
+
npm install
|
|
77
|
+
npm run dev
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Open http://localhost:3000 and load a .blockymodel file.
|
|
81
|
+
|
|
82
|
+
## Keyboard Shortcuts
|
|
83
|
+
|
|
84
|
+
| Key | Action |
|
|
85
|
+
|-----|--------|
|
|
86
|
+
| G | Translate mode |
|
|
87
|
+
| R | Rotate mode |
|
|
88
|
+
| S | Scale mode |
|
|
89
|
+
| Delete | Remove selected node |
|
|
90
|
+
| Ctrl+Z | Undo |
|
|
91
|
+
| Ctrl+Y | Redo |
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### Editor
|
|
96
|
+
|
|
97
|
+
Central state manager that coordinates all editing components.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const editor = new Editor(scene, camera, renderer, orbitControls);
|
|
101
|
+
|
|
102
|
+
// Events
|
|
103
|
+
editor.on('selectionChanged', (object) => { });
|
|
104
|
+
editor.on('objectChanged', (object) => { });
|
|
105
|
+
editor.on('historyChanged', () => { });
|
|
106
|
+
|
|
107
|
+
// Methods
|
|
108
|
+
editor.select(object); // Select an object
|
|
109
|
+
editor.deselect(); // Clear selection
|
|
110
|
+
editor.getSelected(); // Get selected object
|
|
111
|
+
editor.execute(command); // Execute a command (adds to history)
|
|
112
|
+
editor.undo(); // Undo last command
|
|
113
|
+
editor.redo(); // Redo last undone command
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Commands
|
|
117
|
+
|
|
118
|
+
All state changes go through commands for undo/redo support.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import {
|
|
122
|
+
SetPositionCommand,
|
|
123
|
+
SetRotationCommand,
|
|
124
|
+
SetScaleCommand,
|
|
125
|
+
SetPropertyCommand,
|
|
126
|
+
AddNodeCommand,
|
|
127
|
+
RemoveNodeCommand
|
|
128
|
+
} from 'blockymodel-web';
|
|
129
|
+
|
|
130
|
+
// Move an object
|
|
131
|
+
editor.execute(new SetPositionCommand(
|
|
132
|
+
object,
|
|
133
|
+
new THREE.Vector3(10, 0, 0), // new position
|
|
134
|
+
object.position.clone() // old position
|
|
135
|
+
));
|
|
136
|
+
|
|
137
|
+
// Change a property
|
|
138
|
+
editor.execute(new SetPropertyCommand(
|
|
139
|
+
object,
|
|
140
|
+
'userData.customProp', // supports nested paths
|
|
141
|
+
'newValue',
|
|
142
|
+
'oldValue'
|
|
143
|
+
));
|
|
144
|
+
|
|
145
|
+
// Add a child node
|
|
146
|
+
const child = new THREE.Mesh(geometry, material);
|
|
147
|
+
editor.execute(new AddNodeCommand(parent, child));
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Serializer
|
|
151
|
+
|
|
152
|
+
Export the scene back to BlockyModel format.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { Serializer } from 'blockymodel-web';
|
|
156
|
+
|
|
157
|
+
const serializer = new Serializer();
|
|
158
|
+
|
|
159
|
+
// Get as object
|
|
160
|
+
const model = serializer.serialize(scene);
|
|
161
|
+
|
|
162
|
+
// Get as JSON string
|
|
163
|
+
const json = serializer.toJSON(scene);
|
|
164
|
+
|
|
165
|
+
// Download file
|
|
166
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
167
|
+
const url = URL.createObjectURL(blob);
|
|
168
|
+
const a = document.createElement('a');
|
|
169
|
+
a.href = url;
|
|
170
|
+
a.download = 'model.blockymodel';
|
|
171
|
+
a.click();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### BlockyModelLoader
|
|
175
|
+
|
|
176
|
+
Load .blockymodel files into Three.js.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { BlockyModelLoader, applyTextureToModel } from 'blockymodel-web';
|
|
180
|
+
|
|
181
|
+
const loader = new BlockyModelLoader();
|
|
182
|
+
|
|
183
|
+
// Async/await
|
|
184
|
+
const model = await loader.loadAsync('/model.blockymodel');
|
|
185
|
+
scene.add(model);
|
|
186
|
+
|
|
187
|
+
// With texture
|
|
188
|
+
const texture = new THREE.TextureLoader().load('/texture.png');
|
|
189
|
+
applyTextureToModel(model, texture);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## BlockyModel Format
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
interface BlockyModel {
|
|
196
|
+
format?: "character" | "prop";
|
|
197
|
+
lod?: "auto" | string;
|
|
198
|
+
nodes: BlockyNode[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface BlockyNode {
|
|
202
|
+
id: string;
|
|
203
|
+
name: string;
|
|
204
|
+
position?: { x: number; y: number; z: number };
|
|
205
|
+
orientation?: { w: number; x: number; y: number; z: number };
|
|
206
|
+
shape: {
|
|
207
|
+
type: "box" | "quad" | "none";
|
|
208
|
+
offset: { x: number; y: number; z: number };
|
|
209
|
+
stretch: { x: number; y: number; z: number };
|
|
210
|
+
settings: {
|
|
211
|
+
size?: { x: number; y: number; z: number };
|
|
212
|
+
};
|
|
213
|
+
textureLayout: {
|
|
214
|
+
front?: FaceUV;
|
|
215
|
+
back?: FaceUV;
|
|
216
|
+
left?: FaceUV;
|
|
217
|
+
right?: FaceUV;
|
|
218
|
+
top?: FaceUV;
|
|
219
|
+
bottom?: FaceUV;
|
|
220
|
+
};
|
|
221
|
+
visible: boolean;
|
|
222
|
+
doubleSided: boolean;
|
|
223
|
+
shadingMode: "standard" | "flat" | "fullbright" | "reflective";
|
|
224
|
+
};
|
|
225
|
+
children: BlockyNode[];
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Install dependencies
|
|
233
|
+
npm install
|
|
234
|
+
|
|
235
|
+
# Start dev server
|
|
236
|
+
npm run dev
|
|
237
|
+
|
|
238
|
+
# Run tests
|
|
239
|
+
npm run test:run
|
|
240
|
+
|
|
241
|
+
# Run tests with coverage
|
|
242
|
+
npm run test:coverage
|
|
243
|
+
|
|
244
|
+
# Type check
|
|
245
|
+
npm run typecheck
|
|
246
|
+
|
|
247
|
+
# Build for production
|
|
248
|
+
npm run build
|
|
249
|
+
|
|
250
|
+
# Build library for npm
|
|
251
|
+
npm run build:lib
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Project Structure
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
src/
|
|
258
|
+
├── editor/
|
|
259
|
+
│ ├── Editor.ts # Central state manager
|
|
260
|
+
│ ├── SelectionManager.ts # Raycasting + selection
|
|
261
|
+
│ ├── TransformManager.ts # Transform gizmos
|
|
262
|
+
│ ├── History.ts # Undo/redo stack
|
|
263
|
+
│ ├── Serializer.ts # Export to JSON
|
|
264
|
+
│ └── commands/ # Command pattern implementations
|
|
265
|
+
├── ui/
|
|
266
|
+
│ ├── PropertyPanel.ts # Transform/shape editing
|
|
267
|
+
│ ├── HierarchyPanel.ts # Tree view + context menu
|
|
268
|
+
│ └── UVEditor.ts # Per-face UV controls
|
|
269
|
+
├── loaders/
|
|
270
|
+
│ └── BlockyModelLoader.ts
|
|
271
|
+
├── viewer/
|
|
272
|
+
│ └── ViewerController.ts
|
|
273
|
+
└── types/
|
|
274
|
+
└── blockymodel.ts # TypeScript interfaces
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "prop",
|
|
3
|
+
"lod": "auto",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"id": "root",
|
|
7
|
+
"name": "SampleModel",
|
|
8
|
+
"position": { "x": 0, "y": 0, "z": 0 },
|
|
9
|
+
"orientation": { "w": 1, "x": 0, "y": 0, "z": 0 },
|
|
10
|
+
"shape": {
|
|
11
|
+
"type": "none",
|
|
12
|
+
"offset": { "x": 0, "y": 0, "z": 0 },
|
|
13
|
+
"stretch": { "x": 1, "y": 1, "z": 1 },
|
|
14
|
+
"settings": {},
|
|
15
|
+
"textureLayout": {},
|
|
16
|
+
"unwrapMode": "auto",
|
|
17
|
+
"visible": true,
|
|
18
|
+
"doubleSided": false,
|
|
19
|
+
"shadingMode": "standard"
|
|
20
|
+
},
|
|
21
|
+
"children": [
|
|
22
|
+
{
|
|
23
|
+
"id": "body",
|
|
24
|
+
"name": "Body",
|
|
25
|
+
"position": { "x": 0, "y": 8, "z": 0 },
|
|
26
|
+
"orientation": { "w": 1, "x": 0, "y": 0, "z": 0 },
|
|
27
|
+
"shape": {
|
|
28
|
+
"type": "box",
|
|
29
|
+
"offset": { "x": 0, "y": 0, "z": 0 },
|
|
30
|
+
"stretch": { "x": 1, "y": 1, "z": 1 },
|
|
31
|
+
"settings": {
|
|
32
|
+
"size": { "x": 8, "y": 12, "z": 4 }
|
|
33
|
+
},
|
|
34
|
+
"textureLayout": {
|
|
35
|
+
"front": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
36
|
+
"back": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
37
|
+
"left": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
38
|
+
"right": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
39
|
+
"top": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
40
|
+
"bottom": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 }
|
|
41
|
+
},
|
|
42
|
+
"unwrapMode": "custom",
|
|
43
|
+
"visible": true,
|
|
44
|
+
"doubleSided": false,
|
|
45
|
+
"shadingMode": "standard"
|
|
46
|
+
},
|
|
47
|
+
"children": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "head",
|
|
51
|
+
"name": "Head",
|
|
52
|
+
"position": { "x": 0, "y": 18, "z": 0 },
|
|
53
|
+
"orientation": { "w": 1, "x": 0, "y": 0, "z": 0 },
|
|
54
|
+
"shape": {
|
|
55
|
+
"type": "box",
|
|
56
|
+
"offset": { "x": 0, "y": 0, "z": 0 },
|
|
57
|
+
"stretch": { "x": 1, "y": 1, "z": 1 },
|
|
58
|
+
"settings": {
|
|
59
|
+
"size": { "x": 8, "y": 8, "z": 8 }
|
|
60
|
+
},
|
|
61
|
+
"textureLayout": {
|
|
62
|
+
"front": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
63
|
+
"back": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
64
|
+
"left": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
65
|
+
"right": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
66
|
+
"top": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
67
|
+
"bottom": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 }
|
|
68
|
+
},
|
|
69
|
+
"unwrapMode": "custom",
|
|
70
|
+
"visible": true,
|
|
71
|
+
"doubleSided": false,
|
|
72
|
+
"shadingMode": "standard"
|
|
73
|
+
},
|
|
74
|
+
"children": []
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "left_arm",
|
|
78
|
+
"name": "LeftArm",
|
|
79
|
+
"position": { "x": -6, "y": 12, "z": 0 },
|
|
80
|
+
"orientation": { "w": 1, "x": 0, "y": 0, "z": 0 },
|
|
81
|
+
"shape": {
|
|
82
|
+
"type": "box",
|
|
83
|
+
"offset": { "x": 0, "y": 0, "z": 0 },
|
|
84
|
+
"stretch": { "x": 1, "y": 1, "z": 1 },
|
|
85
|
+
"settings": {
|
|
86
|
+
"size": { "x": 4, "y": 12, "z": 4 }
|
|
87
|
+
},
|
|
88
|
+
"textureLayout": {
|
|
89
|
+
"front": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
90
|
+
"back": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
91
|
+
"left": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
92
|
+
"right": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
93
|
+
"top": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
94
|
+
"bottom": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 }
|
|
95
|
+
},
|
|
96
|
+
"unwrapMode": "custom",
|
|
97
|
+
"visible": true,
|
|
98
|
+
"doubleSided": false,
|
|
99
|
+
"shadingMode": "standard"
|
|
100
|
+
},
|
|
101
|
+
"children": []
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "right_arm",
|
|
105
|
+
"name": "RightArm",
|
|
106
|
+
"position": { "x": 6, "y": 12, "z": 0 },
|
|
107
|
+
"orientation": { "w": 1, "x": 0, "y": 0, "z": 0 },
|
|
108
|
+
"shape": {
|
|
109
|
+
"type": "box",
|
|
110
|
+
"offset": { "x": 0, "y": 0, "z": 0 },
|
|
111
|
+
"stretch": { "x": 1, "y": 1, "z": 1 },
|
|
112
|
+
"settings": {
|
|
113
|
+
"size": { "x": 4, "y": 12, "z": 4 }
|
|
114
|
+
},
|
|
115
|
+
"textureLayout": {
|
|
116
|
+
"front": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
117
|
+
"back": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
118
|
+
"left": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
119
|
+
"right": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
120
|
+
"top": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 },
|
|
121
|
+
"bottom": { "offset": { "x": 0, "y": 0 }, "mirror": { "x": false, "y": false }, "angle": 0 }
|
|
122
|
+
},
|
|
123
|
+
"unwrapMode": "custom",
|
|
124
|
+
"visible": true,
|
|
125
|
+
"doubleSided": false,
|
|
126
|
+
"shadingMode": "standard"
|
|
127
|
+
},
|
|
128
|
+
"children": []
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blockymodel-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Web-based 3D editor for BlockyModel format with transform gizmos, hierarchy panel, property editing, and undo/redo",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/blockymodel-web.umd.cjs",
|