@virtualdisplay.io/client 3.6.0 → 4.0.0-alpha.1
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 +46 -74
- package/LICENSE +1 -1
- package/README.md +278 -340
- package/dist/index.d.ts +151 -497
- package/dist/virtualdisplay.client.cjs.js +1 -7
- package/dist/virtualdisplay.client.cjs.js.map +1 -1
- package/dist/virtualdisplay.client.es.js +1 -7859
- package/dist/virtualdisplay.client.es.js.map +1 -1
- package/dist/virtualdisplay.client.umd.js +1 -7
- package/dist/virtualdisplay.client.umd.js.map +1 -1
- package/package.json +14 -98
- package/README.npm.md +0 -483
- package/node_modules/@virtualdisplay-io/logger/CHANGELOG.md +0 -82
- package/node_modules/@virtualdisplay-io/logger/LICENSE +0 -21
- package/node_modules/@virtualdisplay-io/logger/README.md +0 -314
- package/node_modules/@virtualdisplay-io/logger/dist/index.d.ts +0 -167
- package/node_modules/@virtualdisplay-io/logger/dist/virtualdisplay.logger.cjs.js +0 -4
- package/node_modules/@virtualdisplay-io/logger/dist/virtualdisplay.logger.cjs.js.map +0 -1
- package/node_modules/@virtualdisplay-io/logger/dist/virtualdisplay.logger.es.js +0 -377
- package/node_modules/@virtualdisplay-io/logger/dist/virtualdisplay.logger.es.js.map +0 -1
- package/node_modules/@virtualdisplay-io/logger/package.json +0 -79
package/README.md
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
TypeScript library for embedding interactive 3D product models in web
|
|
4
4
|
applications.
|
|
5
5
|
|
|
6
|
-
**Key features:**
|
|
7
|
-
|
|
8
6
|
- Simple attribute-based API for product configurators
|
|
9
|
-
-
|
|
7
|
+
- Server-driven configuration, no client-side mapping needed
|
|
8
|
+
- Fire-and-forget messaging with automatic state synchronisation
|
|
10
9
|
- Zero external dependencies (all bundled)
|
|
11
10
|
- Works with any frontend framework or vanilla JavaScript
|
|
12
11
|
|
|
@@ -20,14 +19,6 @@ pnpm add @virtualdisplay.io/client
|
|
|
20
19
|
yarn add @virtualdisplay.io/client
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
## What's new in v3.1
|
|
24
|
-
|
|
25
|
-
- Simplified message handling with unified event system
|
|
26
|
-
- JSON Schema validation using AJV
|
|
27
|
-
- Cleaner, more maintainable codebase
|
|
28
|
-
- Improved TypeScript types
|
|
29
|
-
- Better error messages with specific error codes
|
|
30
|
-
|
|
31
22
|
## Quick start
|
|
32
23
|
|
|
33
24
|
### Simple product (no options)
|
|
@@ -35,7 +26,7 @@ yarn add @virtualdisplay.io/client
|
|
|
35
26
|
```typescript
|
|
36
27
|
import { VirtualdisplayClient } from '@virtualdisplay.io/client';
|
|
37
28
|
|
|
38
|
-
//
|
|
29
|
+
// Your 3D model is now displayed
|
|
39
30
|
const client = new VirtualdisplayClient({
|
|
40
31
|
parent: '#product-container',
|
|
41
32
|
license: 'your-license-key',
|
|
@@ -48,436 +39,383 @@ const client = new VirtualdisplayClient({
|
|
|
48
39
|
```typescript
|
|
49
40
|
import { VirtualdisplayClient } from '@virtualdisplay.io/client';
|
|
50
41
|
|
|
51
|
-
// Create client instance
|
|
52
42
|
const client = new VirtualdisplayClient({
|
|
53
43
|
parent: '#product-container',
|
|
54
44
|
license: 'your-license-key',
|
|
55
45
|
model: 'sneaker',
|
|
56
46
|
});
|
|
57
47
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
name: 'Color',
|
|
63
|
-
values: [
|
|
64
|
-
{ value: 'Red', nodeIds: ['laces_red', 'sole_red'], isSelected: true },
|
|
65
|
-
{ value: 'Blue', nodeIds: ['laces_blue', 'sole_blue'] },
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Control via product options
|
|
72
|
-
client.getAttribute('Color')?.select('Red');
|
|
73
|
-
|
|
74
|
-
// Or store the attribute for multiple operations
|
|
75
|
-
const sizeAttribute = client.getAttribute('Size');
|
|
76
|
-
if (sizeAttribute) {
|
|
77
|
-
sizeAttribute.select('Large');
|
|
78
|
-
}
|
|
48
|
+
// Select a product option. The 3D model updates automatically.
|
|
49
|
+
// Calls are buffered until the server provides the attribute mapping
|
|
50
|
+
client.select('Color', 'red');
|
|
79
51
|
```
|
|
80
52
|
|
|
53
|
+
The server defines which attributes and values are available for each model.
|
|
54
|
+
The client receives this configuration automatically via postMessage. No
|
|
55
|
+
manual mapping required.
|
|
56
|
+
|
|
81
57
|
## How it works
|
|
82
58
|
|
|
83
59
|
The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated
|
|
84
60
|
from your application. The client library handles all communication via
|
|
85
|
-
postMessage
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
architecture
|
|
95
|
-
- **Event-driven**: Loosely coupled components communicate via events
|
|
96
|
-
|
|
97
|
-
## Product options
|
|
98
|
-
|
|
99
|
-
### When do you need mapping
|
|
100
|
-
|
|
101
|
-
Before diving in, it's important to understand when you need attribute mapping:
|
|
102
|
-
|
|
103
|
-
**No mapping needed:**
|
|
61
|
+
postMessage.
|
|
62
|
+
|
|
63
|
+
```mermaid
|
|
64
|
+
graph LR
|
|
65
|
+
A[Your application] -->|API calls| B[VirtualdisplayClient]
|
|
66
|
+
B -->|postMessage| C[3D server iframe]
|
|
67
|
+
C -->|Events| B
|
|
68
|
+
B -->|State updates| A
|
|
69
|
+
```
|
|
104
70
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
71
|
+
On startup, the server tells the client what is available (attributes, values,
|
|
72
|
+
defaults). After that, the client sends commands and the server responds with
|
|
73
|
+
results.
|
|
74
|
+
|
|
75
|
+
```mermaid
|
|
76
|
+
sequenceDiagram
|
|
77
|
+
participant UI as Your UI
|
|
78
|
+
participant Client as VirtualdisplayClient
|
|
79
|
+
participant Server as 3D server
|
|
80
|
+
|
|
81
|
+
UI ->> Client: new VirtualdisplayClient(options)
|
|
82
|
+
Client ->> Client: Create iframe
|
|
83
|
+
Note right of Client: Outgoing messages queued
|
|
84
|
+
Server ->> Client: READY
|
|
85
|
+
Note right of Client: Queue flushed
|
|
86
|
+
opt UI or camera options configured
|
|
87
|
+
Client ->> Server: CONFIG
|
|
88
|
+
end
|
|
89
|
+
Server ->> Client: MAPPING (attributes + values + defaults)
|
|
90
|
+
Client ->> Client: Store attribute definitions
|
|
91
|
+
Server ->> Client: MUTATION result (defaults, isInitial: true)
|
|
92
|
+
Client ->> UI: onReady + onChange callbacks fire
|
|
93
|
+
```
|
|
110
94
|
|
|
111
|
-
|
|
95
|
+
1. Client creates iframe; outgoing messages are queued until the server is ready.
|
|
96
|
+
2. Server sends `READY` when its message listener is active; client flushes queue.
|
|
97
|
+
3. `CONFIG` is only sent if UI or camera options were provided in the constructor.
|
|
98
|
+
4. Server sends `MAPPING` with attribute definitions, loads the 3D model, applies defaults.
|
|
99
|
+
5. `onReady` fires after the first initial mutation result arrives.
|
|
100
|
+
|
|
101
|
+
## Product configuration
|
|
102
|
+
|
|
103
|
+
Configure product options by selecting attribute values. The server defines
|
|
104
|
+
which attributes exist (e.g. "Color", "Size") and which values each has
|
|
105
|
+
(e.g. "red", "blue"). You select values; the 3D model updates automatically.
|
|
106
|
+
|
|
107
|
+
```mermaid
|
|
108
|
+
sequenceDiagram
|
|
109
|
+
participant UI as Your UI
|
|
110
|
+
participant Client as VirtualdisplayClient
|
|
111
|
+
participant Server as 3D server
|
|
112
|
+
|
|
113
|
+
UI ->> Client: client.select('Color', 'blue')
|
|
114
|
+
Client ->> Server: MUTATION
|
|
115
|
+
Note right of Client: Fire-and-forget
|
|
116
|
+
Server ->> Server: Apply 3D changes
|
|
117
|
+
Server ->> Client: MUTATION result
|
|
118
|
+
alt All succeeded
|
|
119
|
+
Client ->> UI: onChange callbacks fire
|
|
120
|
+
else Some failed
|
|
121
|
+
Client ->> UI: onMutationFailed callbacks fire
|
|
122
|
+
end
|
|
123
|
+
```
|
|
112
124
|
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- Any product where different options show different 3D elements
|
|
125
|
+
- `select()` returns immediately; the result arrives asynchronously.
|
|
126
|
+
- Each attribute in the result has its own `success` flag and optional `error`.
|
|
127
|
+
- Attributes within the same component fail as a group.
|
|
117
128
|
|
|
118
|
-
###
|
|
129
|
+
### Selecting values
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
```typescript
|
|
132
|
+
// Select a product option. The 3D model updates automatically.
|
|
133
|
+
client.select('Color', 'blue');
|
|
134
|
+
client.select('Size', 'large');
|
|
135
|
+
```
|
|
122
136
|
|
|
123
|
-
|
|
137
|
+
Can be called before the server is ready. Mutations are buffered and sent when
|
|
138
|
+
the mapping arrives.
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
- Your business rules (stock, pricing, combinations) change independently from
|
|
127
|
-
the 3D model
|
|
128
|
-
- You control exactly which parts of the model are shown for each option
|
|
129
|
-
- You can group multiple 3D nodes (meshes, materials) into logical product
|
|
130
|
-
choices
|
|
140
|
+
### Tracking changes
|
|
131
141
|
|
|
132
|
-
|
|
133
|
-
the 3D model:
|
|
142
|
+
Keep your UI in sync with the 3D server state:
|
|
134
143
|
|
|
135
144
|
```typescript
|
|
136
|
-
client.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
values: [
|
|
141
|
-
{
|
|
142
|
-
value: 'Red',
|
|
143
|
-
nodeIds: ['mat_red_sole', 'mat_red_laces', 'mat_red_logo'],
|
|
144
|
-
isSelected: true,
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
value: 'Blue',
|
|
148
|
-
nodeIds: ['mat_blue_sole', 'mat_blue_laces', 'mat_blue_logo'],
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: 'Material',
|
|
154
|
-
values: [
|
|
155
|
-
{ value: 'Leather', nodeIds: ['material_leather_upper'] },
|
|
156
|
-
{
|
|
157
|
-
value: 'Canvas',
|
|
158
|
-
nodeIds: ['material_canvas_upper'],
|
|
159
|
-
isSelected: true,
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
],
|
|
145
|
+
client.onChange('Color', (selectedValue) => {
|
|
146
|
+
// selectedValue.value is the selected value (e.g. 'blue')
|
|
147
|
+
// selectedValue.isSelected is true
|
|
148
|
+
updateColorButton(selectedValue.value);
|
|
164
149
|
});
|
|
165
150
|
```
|
|
166
151
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- `attribute` - A product feature like Color, Size, or Material
|
|
170
|
-
- `value` - A specific option like Red, Blue, Small, or Large
|
|
171
|
-
- `nodeIds` - The 3D model parts that represent this option
|
|
172
|
-
- `isSelected` - Whether this option is selected by default
|
|
173
|
-
|
|
174
|
-
**Example benefits:**
|
|
152
|
+
### Querying available values
|
|
175
153
|
|
|
176
154
|
```typescript
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
'material_laces_red', // Red laces material
|
|
183
|
-
'texture_logo_red', // Red logo texture
|
|
184
|
-
'mesh_stitching_red' // Red stitching details
|
|
185
|
-
]
|
|
186
|
-
}
|
|
155
|
+
// All values for an attribute
|
|
156
|
+
const colors = client.getValues('Color');
|
|
157
|
+
colors.forEach((av) => {
|
|
158
|
+
console.log(av.value, av.isSelected);
|
|
159
|
+
});
|
|
187
160
|
|
|
188
|
-
//
|
|
189
|
-
|
|
161
|
+
// A specific value
|
|
162
|
+
const blue = client.getValue('Color', 'blue');
|
|
163
|
+
if (blue?.isSelected) {
|
|
164
|
+
highlightButton('blue');
|
|
165
|
+
}
|
|
190
166
|
```
|
|
191
167
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
- Full control over what's visible without modifying the 3D model
|
|
195
|
-
- Business logic stays in your application, not in the 3D server
|
|
196
|
-
- Easy to update when products or availability changes
|
|
197
|
-
- Group complex 3D structures into simple user choices
|
|
168
|
+
### Direct attribute access
|
|
198
169
|
|
|
199
|
-
|
|
170
|
+
For advanced use cases, get the `Attribute` instance directly:
|
|
200
171
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
172
|
+
```typescript
|
|
173
|
+
const colorAttr = client.getAttribute('Color');
|
|
174
|
+
if (colorAttr) {
|
|
175
|
+
colorAttr.select('blue');
|
|
176
|
+
colorAttr.getValues(); // AttributeValue[]
|
|
177
|
+
colorAttr.getValue('blue'); // AttributeValue | undefined
|
|
178
|
+
colorAttr.onChange((value) => { ... });
|
|
179
|
+
}
|
|
180
|
+
```
|
|
208
181
|
|
|
209
|
-
|
|
182
|
+
### Handling failed mutations
|
|
210
183
|
|
|
211
|
-
|
|
184
|
+
```typescript
|
|
185
|
+
client.onMutationFailed(({ attribute, value }) => {
|
|
186
|
+
console.error(`Mutation failed: ${attribute} = ${value}`);
|
|
187
|
+
});
|
|
188
|
+
```
|
|
212
189
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
190
|
+
## Snapshots
|
|
191
|
+
|
|
192
|
+
Capture the current 3D view as an image. Useful for product shots, social
|
|
193
|
+
sharing, or saving configurations.
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
sequenceDiagram
|
|
197
|
+
participant UI as Your UI
|
|
198
|
+
participant Client as VirtualdisplayClient
|
|
199
|
+
participant Server as 3D server
|
|
200
|
+
|
|
201
|
+
UI ->> Client: client.snapshot.take('photo.png')
|
|
202
|
+
Client ->> Server: SNAPSHOT request
|
|
203
|
+
alt Success
|
|
204
|
+
Server ->> Client: SNAPSHOT data (base64)
|
|
205
|
+
Client ->> UI: onDeveloped callback fires
|
|
206
|
+
else Failure (e.g. no canvas)
|
|
207
|
+
Server ->> Client: SNAPSHOT error
|
|
208
|
+
Client ->> UI: onError callback fires
|
|
209
|
+
end
|
|
210
|
+
```
|
|
217
211
|
|
|
218
|
-
|
|
212
|
+
- Filename is validated client-side (extension, no path traversal).
|
|
213
|
+
- Server always responds, either with image data or an error reason.
|
|
219
214
|
|
|
220
|
-
|
|
215
|
+
### Taking a snapshot
|
|
221
216
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
```typescript
|
|
218
|
+
const photo = client.snapshot.take('product-hero-shot.png');
|
|
219
|
+
|
|
220
|
+
// Register callback for when image is ready
|
|
221
|
+
photo.onDeveloped((photoData) => {
|
|
222
|
+
// photoData.filename - The filename you provided
|
|
223
|
+
// photoData.data - Base64 encoded image data
|
|
224
|
+
const img = document.createElement('img');
|
|
225
|
+
img.src = photoData.data;
|
|
226
|
+
document.body.appendChild(img);
|
|
227
|
+
});
|
|
229
228
|
|
|
230
|
-
|
|
229
|
+
// Handle errors (e.g., canvas not available)
|
|
230
|
+
photo.onError((error) => {
|
|
231
|
+
console.error('Snapshot failed:', error.message);
|
|
232
|
+
});
|
|
233
|
+
```
|
|
231
234
|
|
|
232
|
-
|
|
233
|
-
- You call `getAttribute('Color').select('Blue')`
|
|
234
|
-
- Client sends mutation request to server
|
|
235
|
-
- Your code continues immediately (doesn't wait for server response)
|
|
236
|
-
- Server updates the 3D model
|
|
237
|
-
- Server sends back the confirmed state
|
|
238
|
-
- Local state updates based on server response
|
|
239
|
-
- onChange callbacks fire with the actual state
|
|
240
|
-
- UI stays perfectly synchronized with 3D model
|
|
235
|
+
Filename must end with `.png`. The server always returns PNG data.
|
|
241
236
|
|
|
242
|
-
|
|
243
|
-
interactions. In both cases:
|
|
237
|
+
## Camera
|
|
244
238
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
3. Server sends back confirmed state
|
|
248
|
-
4. Client updates local state based on server response
|
|
249
|
-
5. onChange callbacks fire with the actual state
|
|
239
|
+
Control the 3D viewer's camera position programmatically. Useful for guided
|
|
240
|
+
product tours, preset views, or syncing camera state with your UI.
|
|
250
241
|
|
|
251
|
-
|
|
252
|
-
|
|
242
|
+
```mermaid
|
|
243
|
+
sequenceDiagram
|
|
244
|
+
participant UI as Your UI
|
|
245
|
+
participant Client as VirtualdisplayClient
|
|
246
|
+
participant Server as 3D server
|
|
253
247
|
|
|
254
|
-
|
|
248
|
+
UI ->> Client: client.camera.rotate(45).tilt(30).set()
|
|
249
|
+
Client ->> Server: CAMERA commands
|
|
250
|
+
Server ->> Client: CAMERA position
|
|
251
|
+
Client ->> UI: camera.onChange callbacks fire
|
|
252
|
+
Note right of Server: Position updates also fire on user<br/>interaction with the 3D viewport
|
|
253
|
+
```
|
|
255
254
|
|
|
256
|
-
|
|
255
|
+
- Commands accumulate via chaining; `.set()` sends them all at once.
|
|
256
|
+
- `.reset()` sends immediately without chaining.
|
|
257
|
+
- Position updates are continuous. They fire on both programmatic and user-driven camera movement.
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
### Controlling the camera
|
|
259
260
|
|
|
260
261
|
```typescript
|
|
261
|
-
|
|
262
|
+
// Chain commands, then send with .set()
|
|
263
|
+
client.camera.rotate(45).set();
|
|
264
|
+
client.camera.rotate(90).tilt(45).zoom(150).set();
|
|
262
265
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Update UI when state changes
|
|
266
|
-
updateButton(value.value, value.isSelected);
|
|
267
|
-
};
|
|
268
|
-
});
|
|
266
|
+
// Reset to base position (sends immediately)
|
|
267
|
+
client.camera.reset();
|
|
269
268
|
```
|
|
270
269
|
|
|
271
|
-
###
|
|
270
|
+
### Tracking camera position
|
|
272
271
|
|
|
273
|
-
|
|
272
|
+
The current camera position is updated in real-time as the user orbits, zooms,
|
|
273
|
+
or tilts the model.
|
|
274
274
|
|
|
275
275
|
```typescript
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
client.cameraPosition.onChange((position) => {
|
|
277
|
+
console.log(position.rotation, position.tilt, position.zoom);
|
|
278
|
+
});
|
|
278
279
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
// Read current values directly
|
|
281
|
+
console.log(client.cameraPosition.rotation);
|
|
282
|
+
console.log(client.cameraPosition.tilt);
|
|
283
|
+
console.log(client.cameraPosition.zoom);
|
|
283
284
|
```
|
|
284
285
|
|
|
285
|
-
##
|
|
286
|
+
## UI controls
|
|
286
287
|
|
|
287
|
-
|
|
288
|
+
Toggle built-in viewer elements like AR, fullscreen, and the loading indicator.
|
|
289
|
+
All are visible by default.
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
your CMS alongside your product data:
|
|
291
|
+
### Via constructor
|
|
291
292
|
|
|
292
293
|
```typescript
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
const client = new VirtualdisplayClient({
|
|
295
|
+
parent: '#viewer',
|
|
296
|
+
license: 'your-license',
|
|
297
|
+
model: 'product',
|
|
298
|
+
ui: {
|
|
299
|
+
arEnabled: false,
|
|
300
|
+
loadingIndicatorEnabled: false,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
298
303
|
```
|
|
299
304
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
- Store the complete mapping configuration with each product
|
|
303
|
-
- Update mappings when product options or availability changes
|
|
304
|
-
- Let your CMS handle filtering of unavailable combinations
|
|
305
|
-
- Version your mappings when 3D models are updated
|
|
306
|
-
|
|
307
|
-
## Example
|
|
308
|
-
|
|
309
|
-
See the [color configurator example](./examples/color-configurator/) for a
|
|
310
|
-
complete working implementation that demonstrates:
|
|
311
|
-
|
|
312
|
-
- Setting up the client
|
|
313
|
-
- Defining attribute mappings
|
|
314
|
-
- Binding UI controls to attributes
|
|
315
|
-
- Real-time state synchronization
|
|
316
|
-
|
|
317
|
-
## API reference
|
|
318
|
-
|
|
319
|
-
### ClientOptions
|
|
305
|
+
### At runtime
|
|
320
306
|
|
|
321
307
|
```typescript
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
308
|
+
client.viewer.setArEnabled(false);
|
|
309
|
+
client.viewer.setFullscreenEnabled(true);
|
|
310
|
+
client.viewer.setLoadingIndicatorEnabled(false);
|
|
311
|
+
|
|
312
|
+
// Bulk operations
|
|
313
|
+
client.viewer.hideAllUI();
|
|
314
|
+
client.viewer.showAllUI();
|
|
315
|
+
client.viewer.updateUIConfig({ arEnabled: false, fullscreenEnabled: true });
|
|
329
316
|
```
|
|
330
317
|
|
|
331
|
-
|
|
318
|
+
## Lifecycle
|
|
332
319
|
|
|
333
|
-
|
|
320
|
+
### Ready state
|
|
334
321
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
322
|
+
Use `onReady` to know when the 3D model has loaded. For example, to hide
|
|
323
|
+
your own loading spinner. It is **not** required before calling `select()` or
|
|
324
|
+
`onChange()`.
|
|
338
325
|
|
|
339
326
|
```typescript
|
|
340
|
-
client.
|
|
341
|
-
|
|
342
|
-
{
|
|
343
|
-
name: 'Color',
|
|
344
|
-
values: [
|
|
345
|
-
{ value: 'Red', nodeIds: ['node1', 'node2'], isSelected: true },
|
|
346
|
-
{ value: 'Blue', nodeIds: ['node3', 'node4'] },
|
|
347
|
-
],
|
|
348
|
-
},
|
|
349
|
-
],
|
|
327
|
+
client.onReady(() => {
|
|
328
|
+
document.getElementById('loading-spinner').hidden = true;
|
|
350
329
|
});
|
|
351
330
|
```
|
|
352
331
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
Get an attribute selector for changing values. Returns undefined if the
|
|
356
|
-
attribute doesn't exist.
|
|
332
|
+
### Cleanup
|
|
357
333
|
|
|
358
334
|
```typescript
|
|
359
|
-
|
|
360
|
-
if (colorAttr) {
|
|
361
|
-
colorAttr.select('Blue');
|
|
362
|
-
}
|
|
335
|
+
client.destroy();
|
|
363
336
|
```
|
|
364
337
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Clean up and remove the client connection. Always call this when unmounting your
|
|
368
|
-
component.
|
|
338
|
+
## Constructor options
|
|
369
339
|
|
|
370
340
|
```typescript
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
interface AttributeConfig {
|
|
386
|
-
name: string; // Attribute name (e.g., 'Color')
|
|
387
|
-
values: AttributeValueConfig[]; // Possible values
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
interface AttributeValueConfig {
|
|
391
|
-
value: string; // Value name (e.g., 'Red')
|
|
392
|
-
nodeIds: string[]; // 3D node IDs for this value
|
|
393
|
-
isSelected?: boolean; // Default selection state
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
class AttributeSelector {
|
|
397
|
-
// Properties
|
|
398
|
-
name: string; // Attribute name (getter)
|
|
399
|
-
currentValue: string | undefined; // Current selected value (getter)
|
|
400
|
-
availableValues: string[]; // All possible values (getter)
|
|
401
|
-
|
|
402
|
-
// Methods
|
|
403
|
-
select(value: string): AttributeSelector; // Select a value (chainable)
|
|
404
|
-
onChange(callback: () => void): AttributeSelector; // Register change callback (chainable)
|
|
405
|
-
getValues(): AttributeValue[]; // Get all AttributeValue objects
|
|
406
|
-
getValue(value: string): AttributeValue | undefined; // Get specific AttributeValue
|
|
407
|
-
}
|
|
341
|
+
interface ClientOptions {
|
|
342
|
+
parent: string | HTMLElement; // Where to embed the 3D viewer
|
|
343
|
+
license: string; // License key from your Virtualdisplay account
|
|
344
|
+
model: string; // Which 3D model to load
|
|
345
|
+
debug?: boolean; // Shows console logs for troubleshooting
|
|
346
|
+
language?: string; // UI language: 'nl', 'en', or 'de' (default: 'nl')
|
|
347
|
+
|
|
348
|
+
// UI elements (all visible by default)
|
|
349
|
+
ui?: {
|
|
350
|
+
arEnabled?: boolean;
|
|
351
|
+
fullscreenEnabled?: boolean;
|
|
352
|
+
loadingIndicatorEnabled?: boolean;
|
|
353
|
+
};
|
|
408
354
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
355
|
+
// Camera behaviour
|
|
356
|
+
camera?: {
|
|
357
|
+
initialRotate?: number; // Starting view angle (-180 to 180, 0 = front)
|
|
358
|
+
initialTilt?: number; // Starting elevation (0 = top view, 90 = side view)
|
|
359
|
+
initialZoom?: number; // Starting distance (100 = default, may need adjustment)
|
|
360
|
+
minZoom?: number; // Minimum zoom level constraint
|
|
361
|
+
maxZoom?: number; // Maximum zoom level constraint
|
|
362
|
+
minTilt?: number; // Minimum tilt angle constraint
|
|
363
|
+
maxTilt?: number; // Maximum tilt angle constraint
|
|
364
|
+
};
|
|
415
365
|
}
|
|
416
366
|
```
|
|
417
367
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
The client exports `NodeSelector` for direct node manipulation. This is intended
|
|
421
|
-
for specialized tools like mapping editors and inspectors, not for typical
|
|
422
|
-
product configurators. For product configurators, use the attribute-based API.
|
|
423
|
-
|
|
424
|
-
### Error handling
|
|
368
|
+
## Error codes
|
|
425
369
|
|
|
426
370
|
The client throws `VirtualdisplayError` for known error conditions:
|
|
427
371
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
try {
|
|
436
|
-
client.getAttribute('NonExistent').select('Value');
|
|
437
|
-
} catch (error) {
|
|
438
|
-
if (error instanceof VirtualdisplayError) {
|
|
439
|
-
switch (error.code) {
|
|
440
|
-
case ERROR_CODES.ATTRIBUTE_NOT_FOUND:
|
|
441
|
-
console.error('Attribute does not exist');
|
|
442
|
-
break;
|
|
443
|
-
case ERROR_CODES.NO_MAPPING:
|
|
444
|
-
console.error('Call setMapping() first');
|
|
445
|
-
break;
|
|
446
|
-
// Handle other error codes
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
```
|
|
372
|
+
| Code | Meaning |
|
|
373
|
+
|-----------------------|--------------------------------------------------|
|
|
374
|
+
| `ATTRIBUTE_NOT_FOUND` | Requested attribute doesn't exist in the mapping |
|
|
375
|
+
| `VALUE_NOT_FOUND` | Requested value doesn't exist for attribute |
|
|
376
|
+
| `PARENT_NOT_FOUND` | Parent element for iframe not found |
|
|
377
|
+
| `INVALID_PARAMETER` | Invalid parameter (e.g. snapshot filename) |
|
|
378
|
+
| `SNAPSHOT_FAILED` | Server could not capture the snapshot |
|
|
451
379
|
|
|
452
|
-
|
|
380
|
+
## Real-world example
|
|
453
381
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
382
|
+
```typescript
|
|
383
|
+
const client = new VirtualdisplayClient({
|
|
384
|
+
parent: '#product-viewer',
|
|
385
|
+
license: 'your-license',
|
|
386
|
+
model: 'dining-table',
|
|
387
|
+
});
|
|
459
388
|
|
|
460
|
-
|
|
389
|
+
// Wire up your product option buttons. Selections are buffered until ready.
|
|
390
|
+
document.querySelectorAll('.color-option').forEach((button) => {
|
|
391
|
+
button.addEventListener('click', () => {
|
|
392
|
+
client.select('topMaterial', button.dataset.value);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
461
395
|
|
|
462
|
-
|
|
396
|
+
// Keep buttons in sync with 3D state
|
|
397
|
+
client.onChange('topMaterial', (selectedValue) => {
|
|
398
|
+
document.querySelectorAll('.color-option').forEach((btn) => {
|
|
399
|
+
btn.classList.toggle('active', btn.dataset.value === selectedValue.value);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
```
|
|
463
403
|
|
|
464
404
|
## Troubleshooting
|
|
465
405
|
|
|
466
|
-
### Common issues
|
|
467
|
-
|
|
468
406
|
**Iframe not loading:**
|
|
469
407
|
|
|
470
408
|
- Check that the parent element exists in the DOM
|
|
471
409
|
- Verify your license key is valid
|
|
472
|
-
- Ensure the model ID matches your
|
|
410
|
+
- Ensure the model ID matches your licence
|
|
473
411
|
|
|
474
412
|
**Attributes not working:**
|
|
475
413
|
|
|
476
|
-
- Make sure you call `setMapping()` before using `getAttribute()`
|
|
477
414
|
- Verify attribute names match exactly (case-sensitive)
|
|
478
|
-
- Check that
|
|
415
|
+
- Check that the value exists for the attribute
|
|
416
|
+
- Use `onMutationFailed` to catch server-side errors
|
|
479
417
|
|
|
480
418
|
## Changelog
|
|
481
419
|
|
|
482
|
-
See the [
|
|
483
|
-
for release notes
|
|
420
|
+
See the [npm version history](https://www.npmjs.com/package/@virtualdisplay.io/client?activeTab=versions)
|
|
421
|
+
for release notes.
|