customforge 0.1.0-alpha.1 → 0.1.0
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/CHANGELOG.md +79 -24
- package/LICENSES/NunitoSans-OFL.txt +93 -0
- package/LICENSES/plain-mug-CC-BY-4.0.txt +11 -0
- package/README.md +592 -290
- package/README.zh-CN.md +589 -290
- package/dist/NunitoSans-Variable.ttf +0 -0
- package/dist/ProductCustomizer-4ytA68eg.js +2350 -0
- package/dist/ProductCustomizer-4ytA68eg.js.map +1 -0
- package/dist/bridge/TextureBridge.d.ts +1 -1
- package/dist/core/api.d.ts +94 -0
- package/dist/core/api.d.ts.map +1 -0
- package/dist/core/config.d.ts +3 -12
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/design.d.ts.map +1 -1
- package/dist/core/types.d.ts +217 -8
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/uv.d.ts +8 -0
- package/dist/core/uv.d.ts.map +1 -0
- package/dist/cup_decal_small_margins.glb +0 -0
- package/dist/customizer/ProductCustomizer.d.ts +174 -7
- package/dist/customizer/ProductCustomizer.d.ts.map +1 -1
- package/dist/editor/DesignEditor.d.ts +224 -14
- package/dist/editor/DesignEditor.d.ts.map +1 -1
- package/dist/editor/DesignHistory.d.ts +27 -0
- package/dist/editor/DesignHistory.d.ts.map +1 -0
- package/dist/editor/uvBounds.d.ts +15 -0
- package/dist/editor/uvBounds.d.ts.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1108
- package/dist/index.js.map +1 -1
- package/dist/style.css +2123 -11
- package/dist/viewer/ProductViewer.d.ts +37 -6
- package/dist/viewer/ProductViewer.d.ts.map +1 -1
- package/dist/viewer/uvLayout.d.ts +11 -0
- package/dist/viewer/uvLayout.d.ts.map +1 -0
- package/dist/workbench/CustomForgeWorkbench.d.ts +205 -0
- package/dist/workbench/CustomForgeWorkbench.d.ts.map +1 -0
- package/dist/workbench/assets/catalog.d.ts +8 -0
- package/dist/workbench/assets/catalog.d.ts.map +1 -0
- package/dist/workbench/config.d.ts +96 -0
- package/dist/workbench/config.d.ts.map +1 -0
- package/dist/workbench/icons.d.ts +4 -0
- package/dist/workbench/icons.d.ts.map +1 -0
- package/dist/workbench/index.d.ts +25 -0
- package/dist/workbench/index.d.ts.map +1 -0
- package/dist/workbench/template.d.ts +3 -0
- package/dist/workbench/template.d.ts.map +1 -0
- package/dist/workbench/types.d.ts +542 -0
- package/dist/workbench/types.d.ts.map +1 -0
- package/dist/workbench.js +3052 -0
- package/dist/workbench.js.map +1 -0
- package/package.json +77 -71
package/README.md
CHANGED
|
@@ -1,64 +1,79 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<p>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
</
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="./src/workbench/assets/CustomForgeLogo.png" alt="CustomForge Logo" width="136">
|
|
4
|
+
|
|
5
|
+
<h1>CustomForge</h1>
|
|
6
|
+
|
|
7
|
+
<hr>
|
|
8
|
+
|
|
9
|
+
<p><strong>Turn a 2D texture canvas into a live 3D product preview</strong></p>
|
|
10
|
+
|
|
11
|
+
<p>A lightweight, framework-agnostic toolkit for building browser-based product customization experiences with Fabric.js and Three.js</p>
|
|
12
|
+
|
|
13
|
+
<p>
|
|
14
|
+
<strong>English</strong>
|
|
15
|
+
<span> · </span>
|
|
16
|
+
<a href="./README.zh-CN.md">简体中文</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p>
|
|
20
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-2563EB?style=flat-square&logo=typescript&logoColor=white&labelColor=1F2937">
|
|
21
|
+
<img alt="Vite" src="https://img.shields.io/badge/Vite-8-7C3AED?style=flat-square&logo=vite&logoColor=white&labelColor=1F2937">
|
|
22
|
+
<img alt="Three.js" src="https://img.shields.io/badge/Three.js-r185-27272A?style=flat-square&logo=threedotjs&logoColor=white&labelColor=1F2937">
|
|
23
|
+
<img alt="Fabric.js" src="https://img.shields.io/badge/Fabric.js-7-BE185D?style=flat-square&labelColor=1F2937">
|
|
24
|
+
<img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-0F766E?style=flat-square&logo=apache&logoColor=white&labelColor=1F2937">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Demo
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
## What It Does
|
|
36
|
+
|
|
37
|
+
CustomForge connects a familiar 2D design surface to a UV-mapped 3D model:
|
|
38
|
+
|
|
39
|
+
- Add and edit text on a 2D texture canvas
|
|
40
|
+
- Format selected text through an Office-style contextual toolbar
|
|
41
|
+
- Upload, move, scale, and rotate images
|
|
42
|
+
- Preview every canvas change on a 3D product in real time
|
|
43
|
+
- Load remote GLB/GLTF models and optional base textures
|
|
44
|
+
- Select the customizable surface by mesh name
|
|
45
|
+
- Display the target mesh UV printable region and boundary as a non-exported editor guide
|
|
46
|
+
- Rotate and zoom the 3D preview with OrbitControls
|
|
47
|
+
- Export the composed texture as a PNG
|
|
48
|
+
- Save and restore editable objects through versioned Design JSON
|
|
49
|
+
- Organize objects through names, visibility, locking, and layer order
|
|
50
|
+
- Undo and redo design changes through snapshot history
|
|
51
|
+
- Use configurable typography, background, and decorative asset Dialogs
|
|
52
|
+
- Adapt the default Workbench with branding, labels, theme tokens, and icons
|
|
53
|
+
|
|
54
|
+
The included workbench starts with the bundled `cup_decal_small_margins.glb`, so the project works immediately without fetching an external model
|
|
55
|
+
|
|
56
|
+
## Repository Development
|
|
57
|
+
|
|
58
|
+
Requirements: Node.js 22+ and pnpm 11+
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pnpm install
|
|
62
|
+
pnpm dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Open the URL printed by Vite
|
|
66
|
+
|
|
67
|
+
The built-in demo appears with a UV workspace on the left and a live 3D preview on the right
|
|
68
|
+
|
|
69
|
+
## npm Package
|
|
70
|
+
|
|
71
|
+
CustomForge `0.1.0` is the first non-prerelease version published to the npm Registry. Public APIs follow semantic versioning, and Design JSON version 1 documents remain readable throughout the `0.1.x` line
|
|
72
|
+
|
|
73
|
+
Install the current release with:
|
|
59
74
|
|
|
60
75
|
```powershell
|
|
61
|
-
pnpm add customforge
|
|
76
|
+
pnpm add customforge
|
|
62
77
|
```
|
|
63
78
|
|
|
64
79
|
Fabric.js and Three.js are installed automatically as transitive dependencies
|
|
@@ -76,240 +91,527 @@ Consumer projects can explicitly acknowledge this browser-only choice in `packag
|
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
```
|
|
79
|
-
|
|
80
|
-
Package page: [npmjs.com/package/customforge](https://www.npmjs.com/package/customforge)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
|
|
95
|
+
Package page: [npmjs.com/package/customforge](https://www.npmjs.com/package/customforge)
|
|
96
|
+
|
|
97
|
+
Browser baseline: Chrome and Edge 111+, Firefox 113+, and Safari 16.4+. CustomForge requires ES2022 modules, Canvas 2D, WebGL, `ResizeObserver`, native `dialog`, container queries, and `color-mix()` support
|
|
98
|
+
|
|
99
|
+
For repository-independent verification before publishing, build and create the local package from the repository root:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
pnpm pack:local
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The command builds JavaScript, TypeScript declarations, and public styles, checks the npm file list, and creates:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
customforge-0.1.0.tgz
|
|
92
109
|
```
|
|
93
110
|
|
|
94
111
|
Install that local package in an independent Vite TypeScript project:
|
|
95
112
|
|
|
96
113
|
```powershell
|
|
97
|
-
pnpm add D:\projects\3DRendering\core_code\customforge-0.1.0
|
|
114
|
+
pnpm add D:\projects\3DRendering\core_code\customforge-0.1.0.tgz
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The checked-in `examples/npm-consumer` project imports CustomForge only through this `.tgz`. Run `pnpm install --ignore-workspace` in that directory so pnpm installs it independently from the parent workspace
|
|
118
|
+
|
|
119
|
+
## Load Your Product
|
|
120
|
+
|
|
121
|
+
Choose **Load product** in the demo, then upload a self-contained `.glb` file or
|
|
122
|
+
provide a remote GLB / GLTF URL. Local `.gltf` files are not accepted because
|
|
123
|
+
they can depend on separate binary and texture files
|
|
124
|
+
|
|
125
|
+
The following fields are available under **Advanced options**:
|
|
126
|
+
|
|
127
|
+
| Setting | Purpose |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| Base artwork URL | Optional image placed underneath editable objects and included in the composed texture |
|
|
130
|
+
| Printable mesh name | Name of the mesh that receives the live canvas texture; defaults to `PrintArea` |
|
|
131
|
+
| Flip texture vertically | Enable only when the design appears vertically inverted on a model |
|
|
132
|
+
|
|
133
|
+
UV coordinates are expected to be stored in the 3D model
|
|
134
|
+
|
|
135
|
+
The optional texture image is the visual base layer, not a replacement for model UV data. Without one, the design canvas and printable overlay remain transparent
|
|
136
|
+
|
|
137
|
+
After each product load, the editor reads the target mesh UV coordinates and displays a subtle printable-region fill with its outer boundary above the design canvas. Internal triangulation stays hidden, and the guide is not written into the live texture, Design JSON, or exported PNG
|
|
138
|
+
|
|
139
|
+
> [!IMPORTANT]
|
|
140
|
+
> Remote models, textures, decals, and fonts must be served with CORS headers that allow the app origin
|
|
141
|
+
>
|
|
142
|
+
> A blocked or canvas-tainting resource may fail to load and can prevent PNG export
|
|
143
|
+
|
|
144
|
+
## Basic Usage
|
|
145
|
+
|
|
146
|
+
Use the `ProductCustomizerApi` core when the consuming application owns the complete UI. It can be mounted into any two DOM containers and does not require the Workbench DOM structure or CSS classes:
|
|
147
|
+
|
|
148
|
+
CustomForge is browser-only and must be initialized after its DOM containers are available
|
|
149
|
+
|
|
150
|
+
The editor and viewer require two different containers; call `destroy()` when the page, host component, or instance is no longer in use to release Fabric, WebGL, observer, and event resources
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<div id="texture-editor"></div>
|
|
154
|
+
<div id="product-viewer"></div>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { createCustomizer } from 'customforge'
|
|
159
|
+
import 'customforge/style.css'
|
|
160
|
+
|
|
161
|
+
const customizer = await createCustomizer({
|
|
162
|
+
editor: '#texture-editor',
|
|
163
|
+
viewer: '#product-viewer',
|
|
164
|
+
editorWidth: 1024,
|
|
165
|
+
editorHeight: 512,
|
|
166
|
+
product: {
|
|
167
|
+
modelUrl: 'https://example.com/product.glb',
|
|
168
|
+
textureUrl: 'https://example.com/base-texture.png',
|
|
169
|
+
surfaceMesh: 'PrintArea',
|
|
170
|
+
textureFlipY: false,
|
|
171
|
+
},
|
|
172
|
+
appearance: {
|
|
173
|
+
editor: {
|
|
174
|
+
controlSize: 7,
|
|
175
|
+
objectBorder: '#0057b8',
|
|
176
|
+
uvBoundary: '#d92d20',
|
|
177
|
+
},
|
|
178
|
+
viewer: { backgroundColor: '#f4f4f5' },
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
customizer.addText({
|
|
183
|
+
text: 'Hello world',
|
|
184
|
+
x: 120,
|
|
185
|
+
y: 180,
|
|
186
|
+
fontSize: 64,
|
|
187
|
+
fontWeight: 'bold',
|
|
188
|
+
textAlign: 'center',
|
|
189
|
+
color: '#172126',
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
await customizer.addImage({
|
|
193
|
+
src: 'https://example.com/logo.png',
|
|
194
|
+
x: 720,
|
|
195
|
+
y: 240,
|
|
196
|
+
width: 220,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
window.addEventListener('beforeunload', () => customizer.destroy(), {
|
|
200
|
+
once: true,
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The public package uses the same root and style imports as the local `.tgz`; pin an exact version when reproducible installs are required
|
|
205
|
+
|
|
206
|
+
## Ready-made Workbench
|
|
207
|
+
|
|
208
|
+
Use the optional Workbench entry when a complete default interface is preferable to building controls from scratch
|
|
209
|
+
|
|
210
|
+
The host element must have an explicit height so the design surface, collapsible layers panel, and 3D preview can measure their available space
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<div id="customforge-workbench" style="height: 720px"></div>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { createWorkbench } from 'customforge/workbench'
|
|
218
|
+
import 'customforge/style.css'
|
|
219
|
+
|
|
220
|
+
const workbench = await createWorkbench({
|
|
221
|
+
container: '#customforge-workbench',
|
|
222
|
+
className: 'store-customizer',
|
|
223
|
+
product: {
|
|
224
|
+
modelUrl: 'https://example.com/product.glb',
|
|
225
|
+
surfaceMesh: 'PrintArea',
|
|
226
|
+
},
|
|
227
|
+
features: {
|
|
228
|
+
presetBackgrounds: true,
|
|
229
|
+
presetElements: true,
|
|
230
|
+
},
|
|
231
|
+
layout: {
|
|
232
|
+
header: true,
|
|
233
|
+
layers: true,
|
|
234
|
+
},
|
|
235
|
+
branding: {
|
|
236
|
+
logoUrl: '/brand/logo.png',
|
|
237
|
+
title: 'Studio',
|
|
238
|
+
subtitle: 'Product personalization',
|
|
239
|
+
},
|
|
240
|
+
labels: {
|
|
241
|
+
addText: 'Typography',
|
|
242
|
+
addImage: 'Artwork',
|
|
243
|
+
productDialogTitle: 'Choose a product model',
|
|
244
|
+
loadingProduct: 'Loading product...',
|
|
245
|
+
},
|
|
246
|
+
fontFamilies: [
|
|
247
|
+
{ value: 'Arial', label: 'Arial' },
|
|
248
|
+
{ value: 'Georgia', label: 'Georgia' },
|
|
249
|
+
],
|
|
250
|
+
formatError: (error) =>
|
|
251
|
+
error instanceof Error ? error.message : 'The operation failed',
|
|
252
|
+
theme: {
|
|
253
|
+
accent: '#0057b8',
|
|
254
|
+
accentHover: '#003f87',
|
|
255
|
+
accentContrast: '#ffffff',
|
|
256
|
+
},
|
|
257
|
+
assets: {
|
|
258
|
+
backgrounds: [
|
|
259
|
+
{ id: 'floral', name: 'Floral', url: '/presets/floral.png' },
|
|
260
|
+
],
|
|
261
|
+
elements: [
|
|
262
|
+
{ id: 'flower', name: 'Flower', url: '/presets/flower.png' },
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The bundled CustomForge logo is used by default and can be replaced or hidden through `branding`
|
|
269
|
+
|
|
270
|
+
`labels` covers every fixed Workbench label, Dialog message, placeholder, validation prompt, status, color name, download filename, and accessibility name. `fontFamilies`, `textPresets`, `assets`, and `branding` control the visible names generated from configurable data, while `formatError` converts model, image, UV, and Design JSON failures into user-facing copy
|
|
271
|
+
|
|
272
|
+
CustomForge provides default English copy but does not own application locale state. Build the options from the host application's i18n system and create the Workbench with the active language pack. This keeps locale routing and fallback behavior in the consuming application instead of coupling the library to a specific i18n framework
|
|
273
|
+
|
|
274
|
+
Use the exported `WorkbenchLabels` type when authoring a complete language pack; `WorkbenchOptions.labels` remains partial so applications only overriding a few terms are not forced to repeat the defaults
|
|
275
|
+
|
|
276
|
+
`theme` maps to scoped CSS variables, and `icons` can disable built-in icons or replace individual semantic icons with image URLs
|
|
277
|
+
|
|
278
|
+
`appearance` configures Fabric selection controls, UV helper colors, and the WebGL clear color that normal Workbench CSS cannot style reliably
|
|
279
|
+
|
|
280
|
+
The default UI font stack prefers the rounded `Nunito Sans` family and falls back to system sans-serif fonts
|
|
281
|
+
|
|
282
|
+
CustomForge bundles the font asset and does not fetch third-party font services at runtime
|
|
283
|
+
|
|
284
|
+
Text and image commands open focused Dialogs instead of immediately mutating the canvas
|
|
285
|
+
|
|
286
|
+
- The text Dialog includes an input, color control, and replaceable typography presets
|
|
287
|
+
- Selecting one or more text objects reveals contextual controls inside the existing single-row toolbar for font family, size, bold, italic, underline, alignment, text color, highlight, line height, and letter spacing without shifting the canvas
|
|
288
|
+
- The contextual toolbar supports mixed multi-selection values and an explicit command for entering on-canvas text editing
|
|
289
|
+
- The image Dialog includes local upload, background presets, and decorative element presets
|
|
290
|
+
- A design background replaces the previous design background, fills the current product's UV printable bounds, starts locked at the bottom layer, and persists in Design JSON
|
|
291
|
+
- Selecting an ordinary image reveals a command that converts it into the design background, fits it to the current UV printable bounds, and moves it to the bottom layer
|
|
292
|
+
|
|
293
|
+
Feature and layout switches can also be changed after initialization
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
workbench.setFeature('addText', true)
|
|
297
|
+
workbench.setLayout('header', true)
|
|
298
|
+
workbench.setTheme({ accent: '#0057b8' })
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Extend the Workbench with application actions
|
|
302
|
+
|
|
303
|
+
Register application-owned buttons through `extensions` instead of querying or rewriting Workbench DOM. Each action receives the public Workbench and headless customizer APIs, a current state snapshot, the current selection, and the target layer when applicable:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
const workbench = await createWorkbench({
|
|
307
|
+
container: '#customforge-workbench',
|
|
308
|
+
className: 'store-customizer',
|
|
309
|
+
extensions: [
|
|
310
|
+
{
|
|
311
|
+
id: 'store.export-png',
|
|
312
|
+
placement: 'globalActions',
|
|
313
|
+
label: t('actions.exportPng'),
|
|
314
|
+
variant: 'primary',
|
|
315
|
+
className: 'store-export-command',
|
|
316
|
+
onClick: async ({ customizer, workbench, signal }) => {
|
|
317
|
+
const blob = await customizer.getTextureBlob()
|
|
318
|
+
if (!signal.aborted) {
|
|
319
|
+
workbench.setStatus(`${blob.size} bytes`)
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'store.straighten',
|
|
325
|
+
placement: 'selectionToolbar',
|
|
326
|
+
label: t('actions.straighten'),
|
|
327
|
+
visible: ({ selection }) => selection.length === 1,
|
|
328
|
+
disabled: ({ selection }) => selection.some(({ locked }) => locked),
|
|
329
|
+
onClick: ({ customizer, selection }) => {
|
|
330
|
+
selection.forEach(({ id }) => {
|
|
331
|
+
customizer.updateObjectTransform(id, { rotation: 0 })
|
|
332
|
+
})
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
| Placement | Intended use | Additional context |
|
|
340
|
+
| --- | --- | --- |
|
|
341
|
+
| `globalActions` | Product-, export-, or workflow-level commands | Standard state and selection |
|
|
342
|
+
| `editorToolbar` | Design commands that do not require a selection | Standard state and selection |
|
|
343
|
+
| `selectionToolbar` | Commands for the active selection | Hidden automatically without a selection |
|
|
344
|
+
| `layerActions` | Compact commands repeated for each layer | `layer` contains that row's object |
|
|
345
|
+
|
|
346
|
+
`visible` and `disabled` accept booleans or state predicates. Predicates refresh automatically after CustomForge state, selection, history, and view events; call `workbench.refreshExtensions()` when they also depend on host application state. Promise-returning actions enter a disabled loading state and rejected actions are passed through `formatError` to the existing status region.
|
|
347
|
+
|
|
348
|
+
Use `iconUrl`, `showLabel`, `variant`, `order`, and `className` to control presentation. Extension labels are application data, so pass the active i18n string. Scope deeper CSS with the Workbench `className` and the extension's own `className`:
|
|
349
|
+
|
|
350
|
+
```css
|
|
351
|
+
.store-customizer .store-export-command {
|
|
352
|
+
text-transform: uppercase;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Extensions can also be managed after initialization. The cleanup function only removes the registration that created it:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
const unregister = workbench.registerExtension(action)
|
|
360
|
+
const actions = workbench.getExtensions()
|
|
361
|
+
workbench.removeExtension('store.export-png')
|
|
362
|
+
unregister()
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`workbench.getFeatures()`, `workbench.getLayout()`, `workbench.getTheme()`, and `workbench.getExtensions()` return independent snapshots. `className` is applied only to that Workbench root, so application CSS can target one instance without relying on a global selector. For a fully custom interface, use `createCustomizer()` instead of restyling or querying Workbench internals.
|
|
366
|
+
|
|
367
|
+
| Feature switch | Controls |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| `addText`, `addImage`, `deleteSelection` | Object Dialogs and deletion |
|
|
370
|
+
| `textFormatting` | Contextual text formatting toolbar |
|
|
371
|
+
| `undoRedo` | Undo and redo buttons plus Workbench keyboard shortcuts |
|
|
372
|
+
| `saveDesign`, `loadDesign` | Design JSON actions |
|
|
373
|
+
| `loadRemoteProduct`, `resetView` | Product loading and preview actions |
|
|
374
|
+
| `reorderObjects`, `toggleObjectVisibility`, `lockObjects`, `renameObjects` | Layer management |
|
|
375
|
+
| `presetBackgrounds`, `presetElements` | Image Dialog preset tabs |
|
|
376
|
+
|
|
377
|
+
| Layout switch | Region |
|
|
378
|
+
| --- | --- |
|
|
379
|
+
| `header` | Brand and global product actions |
|
|
380
|
+
| `editorHeader`, `viewerHeader` | Workspace panel headings |
|
|
381
|
+
| `toolbar` | Design tool controls |
|
|
382
|
+
| `layers` | Collapsible object layer panel and toolbar entry |
|
|
383
|
+
| `status` | Runtime status inside the 3D preview |
|
|
384
|
+
|
|
385
|
+
All feature and layout values default to `true`
|
|
386
|
+
|
|
387
|
+
Feature switches only control the built-in Workbench controls and do not remove methods from `workbench.customizer`
|
|
388
|
+
|
|
389
|
+
Theme values are intended for coherent product-level styling rather than per-button color configuration
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
const workbench = await createWorkbench({
|
|
393
|
+
container: '#customforge-workbench',
|
|
394
|
+
icons: {
|
|
395
|
+
enabled: true,
|
|
396
|
+
sources: {
|
|
397
|
+
addText: '/icons/typography.svg',
|
|
398
|
+
loadProduct: '/icons/upload.svg',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
textPresets: [
|
|
402
|
+
{
|
|
403
|
+
id: 'brand-display',
|
|
404
|
+
name: 'Brand display',
|
|
405
|
+
fontFamily: 'Arial',
|
|
406
|
+
fontSize: 72,
|
|
407
|
+
width: 460,
|
|
408
|
+
color: '#17191c',
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Design JSON
|
|
415
|
+
|
|
416
|
+
`saveDesign()` returns a Library-owned, JSON-safe document instead of exposing Fabric.js serialization. `loadDesign()` validates unknown input and restores the editable object stack asynchronously:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
const design = customizer.saveDesign()
|
|
420
|
+
localStorage.setItem('customforge-design', JSON.stringify(design))
|
|
421
|
+
|
|
422
|
+
const savedDesign = localStorage.getItem('customforge-design')
|
|
423
|
+
if (savedDesign) {
|
|
424
|
+
await customizer.loadDesign(JSON.parse(savedDesign))
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The current Schema version is `1`. It stores the logical canvas size and the text and image objects in back-to-front render order, including stable object IDs, names, visibility, locking, image roles, center-based transforms, and rich text formatting. Existing version 1 documents without the optional formatting fields continue to load with the original bold, centered defaults
|
|
429
|
+
|
|
430
|
+
Design JSON intentionally excludes the product model, target mesh, and base texture. A document can only be loaded into an editor with exactly the same logical width and height
|
|
431
|
+
|
|
432
|
+
An image with `role: 'background'` is a design object rather than the product base texture. Only one is allowed, it must be the first object, and older version 1 documents without `role` continue to load as ordinary image elements
|
|
433
|
+
|
|
434
|
+
Loading is transactional: the current design remains unchanged unless the document validates and every referenced image loads successfully. Blob URL images are converted to Data URLs when added; remote image URLs remain URLs and must continue to satisfy browser CORS requirements when restored
|
|
435
|
+
|
|
436
|
+
Design JSON version 1 remains readable throughout the `0.1.x` release line. New optional fields may be added without invalidating existing version 1 documents
|
|
437
|
+
|
|
438
|
+
## Undo and Redo
|
|
439
|
+
|
|
440
|
+
History is enabled in both the headless core and Workbench and defaults to 50 undo steps
|
|
441
|
+
|
|
442
|
+
Set `historyLimit` in `createCustomizer` or `createWorkbench` options to change the retained undo depth
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
if (customizer.canUndo()) {
|
|
446
|
+
await customizer.undo()
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
await customizer.redo()
|
|
450
|
+
customizer.clearHistory()
|
|
451
|
+
|
|
452
|
+
const stop = customizer.on('historychange', ({ canUndo, canRedo }) => {
|
|
453
|
+
console.log({ canUndo, canRedo })
|
|
454
|
+
})
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
History covers object creation, deletion, canvas transforms, text content and formatting edits, image-to-background conversion, layer order, names, visibility, locking, and Design JSON loading
|
|
458
|
+
|
|
459
|
+
Workbench also supports `Ctrl` or `Cmd` + `Z`, `Ctrl` or `Cmd` + `Shift` + `Z`, and `Ctrl` + `Y` while focus is outside form fields
|
|
460
|
+
|
|
461
|
+
Successful product replacement keeps the current design but starts a new history baseline
|
|
462
|
+
|
|
463
|
+
## Instance API
|
|
464
|
+
|
|
465
|
+
| Method | Description |
|
|
466
|
+
| --- | --- |
|
|
467
|
+
| `addText(options)` | Add and select editable text, constrained to the canvas; `fontSize` defaults to `22` |
|
|
468
|
+
| `addImage(options)` | Load and select an element or replace the design background |
|
|
469
|
+
| `getState()` | Return product, canvas, printable bounds, objects, selection, history, and 3D view in one snapshot |
|
|
470
|
+
| `getProduct()` | Return the resolved current product configuration |
|
|
471
|
+
| `getCanvasSize()` | Return the logical texture dimensions |
|
|
472
|
+
| `getPrintableBounds()` | Return the UV printable bounds in logical canvas coordinates |
|
|
473
|
+
| `getObjects()` | Return the current objects in back-to-front layer order |
|
|
474
|
+
| `getSelectedObjectIds()` | Return stable IDs for the current selection |
|
|
475
|
+
| `selectObject(id)` | Select a visible object by stable ID |
|
|
476
|
+
| `selectObjects(ids)` | Select multiple visible objects by stable ID |
|
|
477
|
+
| `clearSelection()` | Clear the current canvas selection without changing the design |
|
|
478
|
+
| `removeObject(id)` | Remove an object by stable ID |
|
|
479
|
+
| `moveObject(id, index)` | Move an object to a zero-based layer index |
|
|
480
|
+
| `setImageAsBackground(id)` | Replace the design background with an existing image and fit it to the current UV printable bounds |
|
|
481
|
+
| `renameObject(id, name)` | Change the object name shown in layer tools |
|
|
482
|
+
| `setObjectVisibility(id, visible)` | Include or exclude an object from rendering |
|
|
483
|
+
| `setObjectLocked(id, locked)` | Lock or unlock canvas transformations |
|
|
484
|
+
| `updateObjectTransform(id, options)` | Update center position, scale, rotation, and flips by stable ID |
|
|
485
|
+
| `updateText(id, options)` | Update text content and formatting by stable ID |
|
|
486
|
+
| `editText(id)` | Enter on-canvas editing for an unlocked text object |
|
|
487
|
+
| `deleteSelected()` | Remove the active object or selection |
|
|
488
|
+
| `saveDesign()` | Return the current versioned Design JSON document |
|
|
489
|
+
| `loadDesign(value)` | Validate and transactionally restore Design JSON |
|
|
490
|
+
| `canUndo()`, `canRedo()` | Query the current history directions |
|
|
491
|
+
| `undo()`, `redo()` | Restore the previous or next design snapshot |
|
|
492
|
+
| `clearHistory()` | Make the current design the new history baseline |
|
|
493
|
+
| `loadProduct(product)` | Replace the model, base texture, and target mesh |
|
|
494
|
+
| `getTextureDataUrl()` | Return the composed texture as a PNG Data URL |
|
|
495
|
+
| `getTextureBlob()` | Return the composed texture as a PNG Blob |
|
|
496
|
+
| `exportTexture(filename?)` | Download the composed texture as PNG |
|
|
497
|
+
| `getViewState()`, `setViewState(state)` | Read or restore the 3D camera position and target |
|
|
498
|
+
| `resetView()` | Restore the default 3D camera position |
|
|
499
|
+
| `on(event, listener)` | Subscribe to instance events; returns an unsubscribe function |
|
|
500
|
+
| `destroy()` | Release DOM events, Fabric state, and WebGL resources |
|
|
501
|
+
|
|
502
|
+
Available events are `ready`, `change`, `selectionchange`, `historychange`, `viewchange`, `status`, and `error`
|
|
503
|
+
|
|
504
|
+
The headless contract is exported as `ProductCustomizerApi`; the default UI contract is exported as `CustomForgeWorkbenchApi`. The concrete classes remain available, but consumers can depend on these interfaces without coupling application code to their implementation.
|
|
505
|
+
|
|
506
|
+
`ProductCustomizer` does not expose its Fabric.js editor or Three.js viewer instances; consumers interact through the facade API
|
|
507
|
+
|
|
508
|
+
Imports from `core`, `editor`, `viewer`, `bridge`, or any other undeclared package subpath are unsupported
|
|
509
|
+
|
|
510
|
+
## Architecture
|
|
511
|
+
|
|
512
|
+
```mermaid
|
|
513
|
+
flowchart LR
|
|
514
|
+
A[Text and images] --> B[DesignEditor]
|
|
515
|
+
C[Base texture] --> B
|
|
516
|
+
B --> D[HTML Canvas]
|
|
517
|
+
D --> E[TextureBridge]
|
|
518
|
+
E --> F[Three.js CanvasTexture]
|
|
519
|
+
G[GLB / GLTF model] --> H[ProductViewer]
|
|
520
|
+
F --> H
|
|
521
|
+
H --> I[Printable mesh]
|
|
98
522
|
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
The Schema is still an alpha contract and may change in later alpha versions
|
|
195
|
-
|
|
196
|
-
## Instance API
|
|
197
|
-
|
|
198
|
-
| Method | Description |
|
|
199
|
-
| --- | --- |
|
|
200
|
-
| `addText(options)` | Add and select editable text, constrained to the canvas |
|
|
201
|
-
| `addImage(options)` | Load and select an image, constrained to the canvas |
|
|
202
|
-
| `deleteSelected()` | Remove the active object or selection |
|
|
203
|
-
| `saveDesign()` | Return the current versioned Design JSON document |
|
|
204
|
-
| `loadDesign(value)` | Validate and transactionally restore Design JSON |
|
|
205
|
-
| `loadProduct(product)` | Replace the model, base texture, and target mesh |
|
|
206
|
-
| `exportTexture(filename?)` | Download the composed texture as PNG |
|
|
207
|
-
| `resetView()` | Restore the default 3D camera position |
|
|
208
|
-
| `on(event, listener)` | Subscribe to instance events; returns an unsubscribe function |
|
|
209
|
-
| `destroy()` | Release DOM events, Fabric state, and WebGL resources |
|
|
210
|
-
|
|
211
|
-
Available events are `ready`, `change`, `selectionchange`, `status`, and `error`
|
|
212
|
-
|
|
213
|
-
The initial alpha stability boundary is limited to the methods above, `createCustomizer`, `ProductCustomizer`, and the configuration, event, and Design JSON types exported from the package root
|
|
214
|
-
|
|
215
|
-
`ProductCustomizer` does not expose its Fabric.js editor or Three.js viewer instances; consumers interact through the facade API
|
|
216
|
-
|
|
217
|
-
Imports from `core`, `editor`, `viewer`, `bridge`, or any other undeclared package subpath are unsupported
|
|
218
|
-
|
|
219
|
-
## Architecture
|
|
220
|
-
|
|
221
|
-
```mermaid
|
|
222
|
-
flowchart LR
|
|
223
|
-
A[Text and images] --> B[DesignEditor]
|
|
224
|
-
C[Base texture] --> B
|
|
225
|
-
B --> D[HTML Canvas]
|
|
226
|
-
D --> E[TextureBridge]
|
|
227
|
-
E --> F[Three.js CanvasTexture]
|
|
228
|
-
G[GLB / GLTF model] --> H[ProductViewer]
|
|
229
|
-
F --> H
|
|
230
|
-
H --> I[Customizable mesh]
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
```text
|
|
234
|
-
ProductCustomizer
|
|
235
|
-
|-- DesignEditor Fabric.js rendering and object interaction
|
|
236
|
-
|-- ProductViewer Three.js scene, model, material, and camera
|
|
237
|
-
`-- TextureBridge Canvas-to-material synchronization
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
`ProductCustomizer` coordinates the modules while keeping Fabric.js and Three.js details behind a small instance API
|
|
241
|
-
|
|
242
|
-
The demo UI uses Vanilla TypeScript; the core does not depend on Vue, React, or another UI framework
|
|
243
|
-
|
|
244
|
-
## Model Contract
|
|
245
|
-
|
|
246
|
-
The current prototype expects:
|
|
247
|
-
|
|
248
|
-
- A GLB or GLTF model with valid UV coordinates
|
|
249
|
-
- An uncompressed model that can be loaded by the standard Three.js `GLTFLoader`
|
|
250
|
-
- A named mesh for the customizable surface, such as `PrintArea`
|
|
251
|
-
- The target texture in the first material slot of that mesh
|
|
252
|
-
- Remote asset URLs accessible under the browser's CORS policy
|
|
253
|
-
|
|
254
|
-
The built-in demo follows the same `PrintArea` mesh convention
|
|
255
|
-
|
|
256
|
-
## Project Structure
|
|
257
|
-
|
|
258
|
-
```text
|
|
259
|
-
src/
|
|
260
|
-
|-- bridge/ Canvas and Three.js texture synchronization
|
|
261
|
-
|-- core/ Public types, configuration, and DOM helpers
|
|
262
|
-
|-- customizer/ Public instance orchestration
|
|
263
|
-
|-- demo/ Runnable workbench UI
|
|
264
|
-
|-- editor/ Fabric.js design surface
|
|
265
|
-
|-- style.css Public Library style entry
|
|
266
|
-
|-- styles/ Library core styles
|
|
267
|
-
|-- viewer/ Three.js product preview
|
|
268
|
-
`-- index.ts Framework-independent source entry
|
|
269
|
-
|
|
270
|
-
examples/
|
|
271
|
-
`-- npm-consumer/ Independent local .tgz consumer
|
|
272
|
-
|
|
273
|
-
scripts/
|
|
274
|
-
`-- verify-package.mjs npm file and artifact boundary checks
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Development Commands
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
pnpm check # TypeScript project check
|
|
281
|
-
pnpm test # Unit tests
|
|
282
|
-
pnpm build # Type-check and production build
|
|
283
|
-
pnpm build:lib # Build ESM, declarations, and core styles
|
|
284
|
-
pnpm verify:package # Check dist and the npm file list
|
|
285
|
-
pnpm pack:local # Build, verify, and create the local .tgz
|
|
286
|
-
pnpm release:check # Run checks, tests, package build, verification, and local pack
|
|
287
|
-
pnpm preview # Preview the production build
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
## Current Scope
|
|
291
|
-
|
|
292
|
-
This is an early technical prototype
|
|
293
|
-
|
|
294
|
-
The public API and design document format are not stable yet
|
|
295
|
-
|
|
296
|
-
The current milestone intentionally focuses on one texture and one customizable mesh
|
|
297
|
-
|
|
298
|
-
Public npm releases use the `alpha` dist-tag until the API and Design JSON contract are ready for a more stable channel
|
|
299
|
-
|
|
300
|
-
Multi-surface products, undo/redo, and framework adapters are not implemented yet. Undo/redo is the next planned milestone and will build on the Design JSON snapshot contract
|
|
301
|
-
|
|
302
|
-
## Technology
|
|
303
|
-
|
|
304
|
-
- [Fabric.js](https://fabricjs.com/) for the 2D editing surface
|
|
305
|
-
- [Three.js](https://threejs.org/) for model loading and real-time 3D rendering
|
|
306
|
-
- [Vite](https://vite.dev/) for development and application builds
|
|
307
|
-
- [TypeScript](https://www.typescriptlang.org/) with strict checking enabled
|
|
308
|
-
|
|
309
|
-
## License
|
|
310
|
-
|
|
311
|
-
CustomForge is licensed under the Apache License 2.0
|
|
312
|
-
|
|
313
|
-
See [LICENSE](./LICENSE) for the full license terms
|
|
314
|
-
|
|
315
|
-
Third-party dependencies and assets remain subject to their respective licenses
|
|
523
|
+
|
|
524
|
+
```text
|
|
525
|
+
ProductCustomizer
|
|
526
|
+
|-- DesignEditor Fabric.js rendering and object interaction
|
|
527
|
+
|-- ProductViewer Three.js scene, model, material, and camera
|
|
528
|
+
`-- TextureBridge Canvas-to-material synchronization
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`ProductCustomizer` coordinates the modules while keeping Fabric.js and Three.js details behind a small instance API
|
|
532
|
+
|
|
533
|
+
The optional Workbench and demo use Vanilla TypeScript; the core does not depend on Vue, React, or another UI framework
|
|
534
|
+
|
|
535
|
+
## Model Contract
|
|
536
|
+
|
|
537
|
+
The current model contract expects:
|
|
538
|
+
|
|
539
|
+
- A GLB or GLTF model with valid UV coordinates
|
|
540
|
+
- An uncompressed model that can be loaded by the standard Three.js `GLTFLoader`
|
|
541
|
+
- A named mesh for the customizable surface, such as `PrintArea`
|
|
542
|
+
- The target texture in the first material slot of that mesh
|
|
543
|
+
- Remote asset URLs accessible under the browser's CORS policy
|
|
544
|
+
|
|
545
|
+
The bundled default model separates the complete cup into `MugBody` and the UV-mapped printable overlay into `PrintArea`. CustomForge preserves the body material and applies the live design texture only to `PrintArea`
|
|
546
|
+
|
|
547
|
+
## Project Structure
|
|
548
|
+
|
|
549
|
+
```text
|
|
550
|
+
src/
|
|
551
|
+
|-- bridge/ Canvas and Three.js texture synchronization
|
|
552
|
+
|-- core/ Public types, configuration, and DOM helpers
|
|
553
|
+
|-- customizer/ Public instance orchestration
|
|
554
|
+
|-- demo/ Runnable workbench UI
|
|
555
|
+
|-- editor/ Fabric.js design surface and snapshot history
|
|
556
|
+
|-- style.css Public Library style entry
|
|
557
|
+
|-- styles/ Core and Workbench styles
|
|
558
|
+
|-- viewer/ Three.js product preview
|
|
559
|
+
|-- workbench/ Configurable UI, Dialogs, icons, and preset assets
|
|
560
|
+
`-- index.ts Framework-independent source entry
|
|
561
|
+
|
|
562
|
+
examples/
|
|
563
|
+
|-- api-contract-consumer/ Public API, custom UI, and browser smoke consumer
|
|
564
|
+
`-- npm-consumer/ Independent local .tgz consumer
|
|
565
|
+
|
|
566
|
+
scripts/
|
|
567
|
+
|-- run-browser-smoke.mjs Headless browser contract gate
|
|
568
|
+
|-- verify-consumers.mjs Independent tarball consumer gate
|
|
569
|
+
|-- verify-package.mjs npm file and artifact boundary checks
|
|
570
|
+
`-- verify-release.mjs Version, changelog, tag, and example checks
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## Development Commands
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
pnpm check # TypeScript project check
|
|
577
|
+
pnpm test # Unit tests
|
|
578
|
+
pnpm build # Type-check and production build
|
|
579
|
+
pnpm build:lib # Build core and Workbench ESM, declarations, and styles
|
|
580
|
+
pnpm verify:release # Check stable version, changelog, tag, and consumer paths
|
|
581
|
+
pnpm verify:package # Check dist and the npm file list
|
|
582
|
+
pnpm verify:consumers # Install the local .tgz into and build both consumers
|
|
583
|
+
pnpm test:browser # Run the packaged API consumer in headless Chrome
|
|
584
|
+
pnpm pack:local # Build, verify, and create the local .tgz through prepack
|
|
585
|
+
pnpm release:check # Run the complete stable release gate
|
|
586
|
+
pnpm preview # Preview the production build
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
See the [release guide](https://github.com/songshanliu/CustomForge/blob/main/RELEASING.md) for npm Trusted Publishing setup, tag rules, the release sequence, and failure handling
|
|
590
|
+
|
|
591
|
+
## Current Scope
|
|
592
|
+
|
|
593
|
+
Version `0.1.0` is the first non-prerelease package. Because the project remains on the `0.x` line, breaking public API changes may be introduced only in a future minor release and will be documented in the changelog
|
|
594
|
+
|
|
595
|
+
The current release intentionally focuses on one logical design canvas mapped to one named Mesh and its first material slot. Design JSON version 1 documents remain readable across `0.1.x`
|
|
596
|
+
|
|
597
|
+
Multi-surface products, advanced alignment tools, and framework adapters are not implemented yet
|
|
598
|
+
|
|
599
|
+
## Technology
|
|
600
|
+
|
|
601
|
+
- [Fabric.js](https://fabricjs.com/) for the 2D editing surface
|
|
602
|
+
- [Three.js](https://threejs.org/) for model loading and real-time 3D rendering
|
|
603
|
+
- [Lucide](https://lucide.dev/) for bundled Workbench controls
|
|
604
|
+
- [Vite](https://vite.dev/) for development and application builds
|
|
605
|
+
- [TypeScript](https://www.typescriptlang.org/) with strict checking enabled
|
|
606
|
+
|
|
607
|
+
## License
|
|
608
|
+
|
|
609
|
+
CustomForge is licensed under the Apache License 2.0
|
|
610
|
+
|
|
611
|
+
See [LICENSE](./LICENSE) for the full license terms
|
|
612
|
+
|
|
613
|
+
Third-party dependencies and assets remain subject to their respective licenses
|
|
614
|
+
|
|
615
|
+
The bundled Nunito Sans font is licensed under the SIL Open Font License 1.1, available in [NunitoSans-OFL.txt](./LICENSES/NunitoSans-OFL.txt)
|
|
616
|
+
|
|
617
|
+
The bundled Plain Mug model by LightSwitch is licensed under CC BY 4.0; attribution and source details are available in [plain-mug-CC-BY-4.0.txt](./LICENSES/plain-mug-CC-BY-4.0.txt)
|