@things-factory/figure-ui 10.1.34 → 10.1.36
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/client/graphql/index.ts +13 -0
- package/client/graphql/v3-store.ts +30 -0
- package/client/modeller/figure-history-panel.ts +29 -2
- package/client/modeller/v3-canvas.ts +145 -0
- package/client/modeller/v3-editor.ts +317 -0
- package/client/modeller/v3-product-session.ts +141 -0
- package/client/modeller/v3-renderer.ts +61 -0
- package/client/pages/figure-modeller-page.ts +50 -3
- package/client/pages/figure-v3-modeller-page.ts +117 -0
- package/client/types.ts +8 -0
- package/dist-client/graphql/index.js +13 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/v3-store.d.ts +3 -0
- package/dist-client/graphql/v3-store.js +32 -0
- package/dist-client/graphql/v3-store.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +2 -0
- package/dist-client/modeller/figure-history-panel.js +28 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/v3-canvas.d.ts +28 -0
- package/dist-client/modeller/v3-canvas.js +157 -0
- package/dist-client/modeller/v3-canvas.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +19 -0
- package/dist-client/modeller/v3-editor.js +332 -0
- package/dist-client/modeller/v3-editor.js.map +1 -0
- package/dist-client/modeller/v3-product-session.d.ts +366 -0
- package/dist-client/modeller/v3-product-session.js +130 -0
- package/dist-client/modeller/v3-product-session.js.map +1 -0
- package/dist-client/modeller/v3-renderer.d.ts +31 -0
- package/dist-client/modeller/v3-renderer.js +81 -0
- package/dist-client/modeller/v3-renderer.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +7 -0
- package/dist-client/pages/figure-modeller-page.js +55 -2
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +26 -0
- package/dist-client/pages/figure-v3-modeller-page.js +140 -0
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +7 -0
- package/dist-client/types.js.map +1 -1
- package/docs/v3-product-cutover.md +35 -0
- package/package.json +4 -4
- package/test/v3-product-session.test.ts +133 -0
- package/test/v3-product-write.test.ts +48 -0
- package/test/v3-renderer.test.ts +24 -0
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { type V3InputChange, type V3PrimitiveCreation, type V3PartEdit } from '@hatiolab/figure-model';
|
|
2
|
+
export interface V3ProductRecord {
|
|
3
|
+
id: string;
|
|
4
|
+
source: string;
|
|
5
|
+
}
|
|
6
|
+
/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */
|
|
7
|
+
export interface V3ProductStore {
|
|
8
|
+
load(id: string): Promise<V3ProductRecord>;
|
|
9
|
+
save(request: {
|
|
10
|
+
id?: string;
|
|
11
|
+
source: string;
|
|
12
|
+
expectedSource?: string;
|
|
13
|
+
creationId?: string;
|
|
14
|
+
}): Promise<V3ProductRecord>;
|
|
15
|
+
}
|
|
16
|
+
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
17
|
+
export declare function createV3ProductSession(store: V3ProductStore): {
|
|
18
|
+
read: () => {
|
|
19
|
+
id: string | undefined;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
saving: boolean;
|
|
22
|
+
error: string | undefined;
|
|
23
|
+
context: null;
|
|
24
|
+
name: null;
|
|
25
|
+
fields: never[];
|
|
26
|
+
geometry: never[];
|
|
27
|
+
bindings: never[];
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
revision: null;
|
|
30
|
+
canUndo?: undefined;
|
|
31
|
+
canRedo?: undefined;
|
|
32
|
+
palette?: undefined;
|
|
33
|
+
} | {
|
|
34
|
+
id: string | undefined;
|
|
35
|
+
loading: boolean;
|
|
36
|
+
saving: boolean;
|
|
37
|
+
error: string | undefined;
|
|
38
|
+
context: string;
|
|
39
|
+
name: string;
|
|
40
|
+
revision: number;
|
|
41
|
+
dirty: boolean;
|
|
42
|
+
canUndo: boolean;
|
|
43
|
+
canRedo: boolean;
|
|
44
|
+
fields: {
|
|
45
|
+
value: number;
|
|
46
|
+
id: string;
|
|
47
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
48
|
+
min: number;
|
|
49
|
+
max: number;
|
|
50
|
+
role?: "design" | "state";
|
|
51
|
+
}[];
|
|
52
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
53
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
54
|
+
palette: Record<string, string>;
|
|
55
|
+
};
|
|
56
|
+
startNew({ discard }?: {
|
|
57
|
+
discard?: boolean | undefined;
|
|
58
|
+
}): {
|
|
59
|
+
id: string | undefined;
|
|
60
|
+
loading: boolean;
|
|
61
|
+
saving: boolean;
|
|
62
|
+
error: string | undefined;
|
|
63
|
+
context: null;
|
|
64
|
+
name: null;
|
|
65
|
+
fields: never[];
|
|
66
|
+
geometry: never[];
|
|
67
|
+
bindings: never[];
|
|
68
|
+
dirty: boolean;
|
|
69
|
+
revision: null;
|
|
70
|
+
canUndo?: undefined;
|
|
71
|
+
canRedo?: undefined;
|
|
72
|
+
palette?: undefined;
|
|
73
|
+
} | {
|
|
74
|
+
id: string | undefined;
|
|
75
|
+
loading: boolean;
|
|
76
|
+
saving: boolean;
|
|
77
|
+
error: string | undefined;
|
|
78
|
+
context: string;
|
|
79
|
+
name: string;
|
|
80
|
+
revision: number;
|
|
81
|
+
dirty: boolean;
|
|
82
|
+
canUndo: boolean;
|
|
83
|
+
canRedo: boolean;
|
|
84
|
+
fields: {
|
|
85
|
+
value: number;
|
|
86
|
+
id: string;
|
|
87
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
88
|
+
min: number;
|
|
89
|
+
max: number;
|
|
90
|
+
role?: "design" | "state";
|
|
91
|
+
}[];
|
|
92
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
93
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
94
|
+
palette: Record<string, string>;
|
|
95
|
+
};
|
|
96
|
+
open(nextId: string, { discard }?: {
|
|
97
|
+
discard?: boolean | undefined;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
id: string | undefined;
|
|
100
|
+
loading: boolean;
|
|
101
|
+
saving: boolean;
|
|
102
|
+
error: string | undefined;
|
|
103
|
+
context: null;
|
|
104
|
+
name: null;
|
|
105
|
+
fields: never[];
|
|
106
|
+
geometry: never[];
|
|
107
|
+
bindings: never[];
|
|
108
|
+
dirty: boolean;
|
|
109
|
+
revision: null;
|
|
110
|
+
canUndo?: undefined;
|
|
111
|
+
canRedo?: undefined;
|
|
112
|
+
palette?: undefined;
|
|
113
|
+
} | {
|
|
114
|
+
id: string | undefined;
|
|
115
|
+
loading: boolean;
|
|
116
|
+
saving: boolean;
|
|
117
|
+
error: string | undefined;
|
|
118
|
+
context: string;
|
|
119
|
+
name: string;
|
|
120
|
+
revision: number;
|
|
121
|
+
dirty: boolean;
|
|
122
|
+
canUndo: boolean;
|
|
123
|
+
canRedo: boolean;
|
|
124
|
+
fields: {
|
|
125
|
+
value: number;
|
|
126
|
+
id: string;
|
|
127
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
128
|
+
min: number;
|
|
129
|
+
max: number;
|
|
130
|
+
role?: "design" | "state";
|
|
131
|
+
}[];
|
|
132
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
133
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
134
|
+
palette: Record<string, string>;
|
|
135
|
+
}>;
|
|
136
|
+
close({ discard }?: {
|
|
137
|
+
discard?: boolean | undefined;
|
|
138
|
+
}): {
|
|
139
|
+
id: string | undefined;
|
|
140
|
+
loading: boolean;
|
|
141
|
+
saving: boolean;
|
|
142
|
+
error: string | undefined;
|
|
143
|
+
context: null;
|
|
144
|
+
name: null;
|
|
145
|
+
fields: never[];
|
|
146
|
+
geometry: never[];
|
|
147
|
+
bindings: never[];
|
|
148
|
+
dirty: boolean;
|
|
149
|
+
revision: null;
|
|
150
|
+
canUndo?: undefined;
|
|
151
|
+
canRedo?: undefined;
|
|
152
|
+
palette?: undefined;
|
|
153
|
+
} | {
|
|
154
|
+
id: string | undefined;
|
|
155
|
+
loading: boolean;
|
|
156
|
+
saving: boolean;
|
|
157
|
+
error: string | undefined;
|
|
158
|
+
context: string;
|
|
159
|
+
name: string;
|
|
160
|
+
revision: number;
|
|
161
|
+
dirty: boolean;
|
|
162
|
+
canUndo: boolean;
|
|
163
|
+
canRedo: boolean;
|
|
164
|
+
fields: {
|
|
165
|
+
value: number;
|
|
166
|
+
id: string;
|
|
167
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
168
|
+
min: number;
|
|
169
|
+
max: number;
|
|
170
|
+
role?: "design" | "state";
|
|
171
|
+
}[];
|
|
172
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
173
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
174
|
+
palette: Record<string, string>;
|
|
175
|
+
};
|
|
176
|
+
apply(changes: V3InputChange[], origin?: "ui" | "ai", expectedRevision?: number, context?: string): {
|
|
177
|
+
context: null;
|
|
178
|
+
name: null;
|
|
179
|
+
fields: never[];
|
|
180
|
+
geometry: never[];
|
|
181
|
+
bindings: never[];
|
|
182
|
+
dirty: boolean;
|
|
183
|
+
revision: null;
|
|
184
|
+
canUndo?: undefined;
|
|
185
|
+
canRedo?: undefined;
|
|
186
|
+
palette?: undefined;
|
|
187
|
+
} | {
|
|
188
|
+
context: string;
|
|
189
|
+
name: string;
|
|
190
|
+
revision: number;
|
|
191
|
+
dirty: boolean;
|
|
192
|
+
canUndo: boolean;
|
|
193
|
+
canRedo: boolean;
|
|
194
|
+
fields: {
|
|
195
|
+
value: number;
|
|
196
|
+
id: string;
|
|
197
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
198
|
+
min: number;
|
|
199
|
+
max: number;
|
|
200
|
+
role?: "design" | "state";
|
|
201
|
+
}[];
|
|
202
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
203
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
204
|
+
palette: Record<string, string>;
|
|
205
|
+
};
|
|
206
|
+
add(part: V3PrimitiveCreation): {
|
|
207
|
+
context: null;
|
|
208
|
+
name: null;
|
|
209
|
+
fields: never[];
|
|
210
|
+
geometry: never[];
|
|
211
|
+
bindings: never[];
|
|
212
|
+
dirty: boolean;
|
|
213
|
+
revision: null;
|
|
214
|
+
canUndo?: undefined;
|
|
215
|
+
canRedo?: undefined;
|
|
216
|
+
palette?: undefined;
|
|
217
|
+
} | {
|
|
218
|
+
context: string;
|
|
219
|
+
name: string;
|
|
220
|
+
revision: number;
|
|
221
|
+
dirty: boolean;
|
|
222
|
+
canUndo: boolean;
|
|
223
|
+
canRedo: boolean;
|
|
224
|
+
fields: {
|
|
225
|
+
value: number;
|
|
226
|
+
id: string;
|
|
227
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
228
|
+
min: number;
|
|
229
|
+
max: number;
|
|
230
|
+
role?: "design" | "state";
|
|
231
|
+
}[];
|
|
232
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
233
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
234
|
+
palette: Record<string, string>;
|
|
235
|
+
};
|
|
236
|
+
edit(edit: V3PartEdit): {
|
|
237
|
+
context: null;
|
|
238
|
+
name: null;
|
|
239
|
+
fields: never[];
|
|
240
|
+
geometry: never[];
|
|
241
|
+
bindings: never[];
|
|
242
|
+
dirty: boolean;
|
|
243
|
+
revision: null;
|
|
244
|
+
canUndo?: undefined;
|
|
245
|
+
canRedo?: undefined;
|
|
246
|
+
palette?: undefined;
|
|
247
|
+
} | {
|
|
248
|
+
context: string;
|
|
249
|
+
name: string;
|
|
250
|
+
revision: number;
|
|
251
|
+
dirty: boolean;
|
|
252
|
+
canUndo: boolean;
|
|
253
|
+
canRedo: boolean;
|
|
254
|
+
fields: {
|
|
255
|
+
value: number;
|
|
256
|
+
id: string;
|
|
257
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
258
|
+
min: number;
|
|
259
|
+
max: number;
|
|
260
|
+
role?: "design" | "state";
|
|
261
|
+
}[];
|
|
262
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
263
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
264
|
+
palette: Record<string, string>;
|
|
265
|
+
};
|
|
266
|
+
undo(): {
|
|
267
|
+
context: null;
|
|
268
|
+
name: null;
|
|
269
|
+
fields: never[];
|
|
270
|
+
geometry: never[];
|
|
271
|
+
bindings: never[];
|
|
272
|
+
dirty: boolean;
|
|
273
|
+
revision: null;
|
|
274
|
+
canUndo?: undefined;
|
|
275
|
+
canRedo?: undefined;
|
|
276
|
+
palette?: undefined;
|
|
277
|
+
} | {
|
|
278
|
+
context: string;
|
|
279
|
+
name: string;
|
|
280
|
+
revision: number;
|
|
281
|
+
dirty: boolean;
|
|
282
|
+
canUndo: boolean;
|
|
283
|
+
canRedo: boolean;
|
|
284
|
+
fields: {
|
|
285
|
+
value: number;
|
|
286
|
+
id: string;
|
|
287
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
288
|
+
min: number;
|
|
289
|
+
max: number;
|
|
290
|
+
role?: "design" | "state";
|
|
291
|
+
}[];
|
|
292
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
293
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
294
|
+
palette: Record<string, string>;
|
|
295
|
+
};
|
|
296
|
+
redo(): {
|
|
297
|
+
context: null;
|
|
298
|
+
name: null;
|
|
299
|
+
fields: never[];
|
|
300
|
+
geometry: never[];
|
|
301
|
+
bindings: never[];
|
|
302
|
+
dirty: boolean;
|
|
303
|
+
revision: null;
|
|
304
|
+
canUndo?: undefined;
|
|
305
|
+
canRedo?: undefined;
|
|
306
|
+
palette?: undefined;
|
|
307
|
+
} | {
|
|
308
|
+
context: string;
|
|
309
|
+
name: string;
|
|
310
|
+
revision: number;
|
|
311
|
+
dirty: boolean;
|
|
312
|
+
canUndo: boolean;
|
|
313
|
+
canRedo: boolean;
|
|
314
|
+
fields: {
|
|
315
|
+
value: number;
|
|
316
|
+
id: string;
|
|
317
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
318
|
+
min: number;
|
|
319
|
+
max: number;
|
|
320
|
+
role?: "design" | "state";
|
|
321
|
+
}[];
|
|
322
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
323
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
324
|
+
palette: Record<string, string>;
|
|
325
|
+
};
|
|
326
|
+
source(): string;
|
|
327
|
+
save(): Promise<{
|
|
328
|
+
id: string | undefined;
|
|
329
|
+
loading: boolean;
|
|
330
|
+
saving: boolean;
|
|
331
|
+
error: string | undefined;
|
|
332
|
+
context: null;
|
|
333
|
+
name: null;
|
|
334
|
+
fields: never[];
|
|
335
|
+
geometry: never[];
|
|
336
|
+
bindings: never[];
|
|
337
|
+
dirty: boolean;
|
|
338
|
+
revision: null;
|
|
339
|
+
canUndo?: undefined;
|
|
340
|
+
canRedo?: undefined;
|
|
341
|
+
palette?: undefined;
|
|
342
|
+
} | {
|
|
343
|
+
id: string | undefined;
|
|
344
|
+
loading: boolean;
|
|
345
|
+
saving: boolean;
|
|
346
|
+
error: string | undefined;
|
|
347
|
+
context: string;
|
|
348
|
+
name: string;
|
|
349
|
+
revision: number;
|
|
350
|
+
dirty: boolean;
|
|
351
|
+
canUndo: boolean;
|
|
352
|
+
canRedo: boolean;
|
|
353
|
+
fields: {
|
|
354
|
+
value: number;
|
|
355
|
+
id: string;
|
|
356
|
+
unit: import("@hatiolab/figure-model").V3Unit;
|
|
357
|
+
min: number;
|
|
358
|
+
max: number;
|
|
359
|
+
role?: "design" | "state";
|
|
360
|
+
}[];
|
|
361
|
+
geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
|
|
362
|
+
bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
|
|
363
|
+
palette: Record<string, string>;
|
|
364
|
+
}>;
|
|
365
|
+
};
|
|
366
|
+
export type V3ProductSession = ReturnType<typeof createV3ProductSession>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createV3Asset, createV3EditorWorkspace, serializeV3Asset, parseV3Asset } from '@hatiolab/figure-model';
|
|
2
|
+
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
3
|
+
export function createV3ProductSession(store) {
|
|
4
|
+
const workspace = createV3EditorWorkspace();
|
|
5
|
+
let epoch = 0, id, baseline;
|
|
6
|
+
let loading = false, saving = false, error;
|
|
7
|
+
let creationId;
|
|
8
|
+
function read() {
|
|
9
|
+
return { ...workspace.read(), id, loading, saving, error };
|
|
10
|
+
}
|
|
11
|
+
function writable() {
|
|
12
|
+
const view = workspace.read();
|
|
13
|
+
if (loading || error || !view.context || view.revision === null)
|
|
14
|
+
throw Error('먼저 모델을 정상적으로 열어 주세요.');
|
|
15
|
+
// Serializing writes also makes create retries safe: no second create while the first is pending.
|
|
16
|
+
if (saving)
|
|
17
|
+
throw Error('저장이 끝난 뒤 편집해 주세요.');
|
|
18
|
+
return { context: view.context, expectedRevision: view.revision, origin: 'ui' };
|
|
19
|
+
}
|
|
20
|
+
function clear() {
|
|
21
|
+
workspace.close({ discardDesignChanges: true });
|
|
22
|
+
id = undefined;
|
|
23
|
+
baseline = undefined;
|
|
24
|
+
error = undefined;
|
|
25
|
+
saving = false;
|
|
26
|
+
creationId = undefined;
|
|
27
|
+
}
|
|
28
|
+
function canLeave(discard) {
|
|
29
|
+
if (saving)
|
|
30
|
+
throw Error('저장이 끝난 뒤 모델을 전환해 주세요.');
|
|
31
|
+
if (workspace.read().dirty && !discard)
|
|
32
|
+
throw Error('저장하지 않은 변경이 있습니다.');
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
read,
|
|
36
|
+
startNew({ discard = false } = {}) {
|
|
37
|
+
canLeave(discard);
|
|
38
|
+
++epoch;
|
|
39
|
+
clear();
|
|
40
|
+
loading = false;
|
|
41
|
+
workspace.open(serializeV3Asset(createV3Asset('새 Figure')));
|
|
42
|
+
creationId = crypto.randomUUID();
|
|
43
|
+
return read();
|
|
44
|
+
},
|
|
45
|
+
async open(nextId, { discard = false } = {}) {
|
|
46
|
+
canLeave(discard);
|
|
47
|
+
const requestEpoch = ++epoch;
|
|
48
|
+
clear();
|
|
49
|
+
loading = true;
|
|
50
|
+
try {
|
|
51
|
+
const record = await store.load(nextId);
|
|
52
|
+
if (requestEpoch !== epoch)
|
|
53
|
+
return read();
|
|
54
|
+
if (record.id !== nextId)
|
|
55
|
+
throw Error('요청한 Figure와 응답의 대상이 다릅니다.');
|
|
56
|
+
workspace.open(record.source);
|
|
57
|
+
id = nextId;
|
|
58
|
+
baseline = record.source;
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
if (requestEpoch === epoch)
|
|
62
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (requestEpoch === epoch)
|
|
66
|
+
loading = false;
|
|
67
|
+
}
|
|
68
|
+
return read();
|
|
69
|
+
},
|
|
70
|
+
close({ discard = false } = {}) {
|
|
71
|
+
canLeave(discard);
|
|
72
|
+
++epoch;
|
|
73
|
+
clear();
|
|
74
|
+
loading = false;
|
|
75
|
+
return read();
|
|
76
|
+
},
|
|
77
|
+
apply(changes, origin = 'ui', expectedRevision, context) {
|
|
78
|
+
const control = writable();
|
|
79
|
+
// AI must supply the context AND revision captured when it started. Never substitute current values.
|
|
80
|
+
if (origin === 'ai' && (context === undefined || expectedRevision === undefined))
|
|
81
|
+
throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.');
|
|
82
|
+
return workspace.apply({
|
|
83
|
+
...control,
|
|
84
|
+
origin,
|
|
85
|
+
context: context ?? control.context,
|
|
86
|
+
expectedRevision: expectedRevision ?? control.expectedRevision,
|
|
87
|
+
changes
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
add(part) {
|
|
91
|
+
return workspace.addPrimitive({ ...writable(), part });
|
|
92
|
+
},
|
|
93
|
+
edit(edit) {
|
|
94
|
+
return workspace.editPart({ ...writable(), edit });
|
|
95
|
+
},
|
|
96
|
+
undo() {
|
|
97
|
+
return workspace.undo(writable());
|
|
98
|
+
},
|
|
99
|
+
redo() {
|
|
100
|
+
return workspace.redo(writable());
|
|
101
|
+
},
|
|
102
|
+
source() {
|
|
103
|
+
const { context } = writable();
|
|
104
|
+
return workspace.export({ context }).text;
|
|
105
|
+
},
|
|
106
|
+
async save() {
|
|
107
|
+
const control = writable(), requestEpoch = epoch;
|
|
108
|
+
const exported = workspace.export(control);
|
|
109
|
+
saving = true;
|
|
110
|
+
try {
|
|
111
|
+
const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId });
|
|
112
|
+
if (requestEpoch !== epoch)
|
|
113
|
+
return read();
|
|
114
|
+
if (!record.id || (id !== undefined && record.id !== id))
|
|
115
|
+
throw Error('저장 응답의 Figure 대상이 다릅니다.');
|
|
116
|
+
if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)
|
|
117
|
+
throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.');
|
|
118
|
+
workspace.acknowledgeSaved(exported);
|
|
119
|
+
id = record.id;
|
|
120
|
+
baseline = record.source;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
if (requestEpoch === epoch)
|
|
124
|
+
saving = false;
|
|
125
|
+
}
|
|
126
|
+
return read();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=v3-product-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-product-session.js","sourceRoot":"","sources":["../../client/modeller/v3-product-session.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EAIb,MAAM,wBAAwB,CAAA;AAa/B,iGAAiG;AACjG,MAAM,UAAU,sBAAsB,CAAC,KAAqB;IAC1D,MAAM,SAAS,GAAG,uBAAuB,EAAE,CAAA;IAC3C,IAAI,KAAK,GAAG,CAAC,EACX,EAAsB,EACtB,QAA4B,CAAA;IAC9B,IAAI,OAAO,GAAG,KAAK,EACjB,MAAM,GAAG,KAAK,EACd,KAAyB,CAAA;IAC3B,IAAI,UAA8B,CAAA;IAClC,SAAS,IAAI;QACX,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC5D,CAAC;IACD,SAAS,QAAQ;QACf,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;QAC7B,IAAI,OAAO,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACpG,kGAAkG;QAClG,IAAI,MAAM;YAAE,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAa,EAAE,CAAA;IAC1F,CAAC;IACD,SAAS,KAAK;QACZ,SAAS,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,EAAE,GAAG,SAAS,CAAA;QACd,QAAQ,GAAG,SAAS,CAAA;QACpB,KAAK,GAAG,SAAS,CAAA;QACjB,MAAM,GAAG,KAAK,CAAA;QACd,UAAU,GAAG,SAAS,CAAA;IACxB,CAAC;IACD,SAAS,QAAQ,CAAC,OAAgB;QAChC,IAAI,MAAM;YAAE,MAAM,KAAK,CAAC,uBAAuB,CAAC,CAAA;QAChD,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAA;IAC1E,CAAC;IACD,OAAO;QACL,IAAI;QACJ,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YAC/B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,EAAE,KAAK,CAAA;YACP,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,KAAK,CAAA;YACf,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;YAC3D,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;YAChC,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YACjD,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,MAAM,YAAY,GAAG,EAAE,KAAK,CAAA;YAC5B,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,IAAI,CAAA;YACd,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBACvC,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,IAAI,EAAE,CAAA;gBACzC,IAAI,MAAM,CAAC,EAAE,KAAK,MAAM;oBAAE,MAAM,KAAK,CAAC,2BAA2B,CAAC,CAAA;gBAClE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBAC7B,EAAE,GAAG,MAAM,CAAA;gBACX,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAA;YAC1B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,YAAY,KAAK,KAAK;oBAAE,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5F,CAAC;oBAAS,CAAC;gBACT,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,GAAG,KAAK,CAAA;YAC7C,CAAC;YACD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YAC5B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,EAAE,KAAK,CAAA;YACP,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,KAAK,CAAA;YACf,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,OAAwB,EAAE,SAAsB,IAAI,EAAE,gBAAyB,EAAE,OAAgB;YACrG,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAA;YAC1B,qGAAqG;YACrG,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,gBAAgB,KAAK,SAAS,CAAC;gBAC9E,MAAM,KAAK,CAAC,mCAAmC,CAAC,CAAA;YAClD,OAAO,SAAS,CAAC,KAAK,CAAC;gBACrB,GAAG,OAAO;gBACV,MAAM;gBACN,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO;gBACnC,gBAAgB,EAAE,gBAAgB,IAAI,OAAO,CAAC,gBAAgB;gBAC9D,OAAO;aACR,CAAC,CAAA;QACJ,CAAC;QACD,GAAG,CAAC,IAAyB;YAC3B,OAAO,SAAS,CAAC,YAAY,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACxD,CAAC;QACD,IAAI,CAAC,IAAgB;YACnB,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,CAAC;QACD,IAAI;YACF,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACnC,CAAC;QACD,IAAI;YACF,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACnC,CAAC;QACD,MAAM;YACJ,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;YAC9B,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAA;QAC3C,CAAC;QACD,KAAK,CAAC,IAAI;YACR,MAAM,OAAO,GAAG,QAAQ,EAAE,EACxB,YAAY,GAAG,KAAK,CAAA;YACtB,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,GAAG,IAAI,CAAA;YACb,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;gBACpG,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,IAAI,EAAE,CAAA;gBACzC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;oBAAE,MAAM,KAAK,CAAC,yBAAyB,CAAC,CAAA;gBAChG,IAAI,gBAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI;oBACjE,MAAM,KAAK,CAAC,wCAAwC,CAAC,CAAA;gBACvD,SAAS,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAA;gBACpC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAA;gBACd,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAA;YAC1B,CAAC;oBAAS,CAAC;gBACT,IAAI,YAAY,KAAK,KAAK;oBAAE,MAAM,GAAG,KAAK,CAAA;YAC5C,CAAC;YACD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {\n createV3Asset,\n createV3EditorWorkspace,\n serializeV3Asset,\n parseV3Asset,\n type V3InputChange,\n type V3PrimitiveCreation,\n type V3PartEdit\n} from '@hatiolab/figure-model'\n\nexport interface V3ProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface V3ProductStore {\n load(id: string): Promise<V3ProductRecord>\n save(request: { id?: string; source: string; expectedSource?: string; creationId?: string }): Promise<V3ProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createV3ProductSession(store: V3ProductStore) {\n const workspace = createV3EditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeV3Asset(createV3Asset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: V3InputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n },\n add(part: V3PrimitiveCreation) {\n return workspace.addPrimitive({ ...writable(), part })\n },\n edit(edit: V3PartEdit) {\n return workspace.editPart({ ...writable(), edit })\n },\n undo() {\n return workspace.undo(writable())\n },\n redo() {\n return workspace.redo(writable())\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n async save() {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n saving = true\n try {\n const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId })\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n workspace.acknowledgeSaved(exported)\n id = record.id\n baseline = record.source\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type V3ProductSession = ReturnType<typeof createV3ProductSession>\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/** Evaluated V3 boundary: graph values are resolved before reaching the renderer. */
|
|
3
|
+
export interface V3RenderPart {
|
|
4
|
+
id: string;
|
|
5
|
+
shape: {
|
|
6
|
+
provider: string;
|
|
7
|
+
dimensions: Record<string, number>;
|
|
8
|
+
profile?: Array<{
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
pose: {
|
|
14
|
+
r: number[][];
|
|
15
|
+
t: number[];
|
|
16
|
+
};
|
|
17
|
+
material: {
|
|
18
|
+
token: string;
|
|
19
|
+
preset?: string;
|
|
20
|
+
transparent?: boolean;
|
|
21
|
+
emissive?: {
|
|
22
|
+
token: string;
|
|
23
|
+
intensity: number;
|
|
24
|
+
on: boolean;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
segments?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function disposeV3Meshes(group: THREE.Group): void;
|
|
30
|
+
/** Build first, then swap; failed rendering must not erase the previous scene. No old meshes survive a successful swap. */
|
|
31
|
+
export declare function replaceV3Meshes(group: THREE.Group, parts: readonly V3RenderPart[], palette: Record<string, string>): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
function geometry(part) {
|
|
3
|
+
const d = part.shape.dimensions;
|
|
4
|
+
let g;
|
|
5
|
+
if (part.shape.provider === 'box')
|
|
6
|
+
g = new THREE.BoxGeometry(d.width, d.height, d.depth);
|
|
7
|
+
else if (part.shape.provider === 'cylinder')
|
|
8
|
+
g = new THREE.CylinderGeometry(d.radius, d.radius, d.length, part.segments ?? 12);
|
|
9
|
+
else if (part.shape.provider === 'roundedBox') {
|
|
10
|
+
const w = d.width / 2, z = d.depth / 2, r = d.round, s = new THREE.Shape();
|
|
11
|
+
s.moveTo(-w + r, -z);
|
|
12
|
+
s.lineTo(w - r, -z);
|
|
13
|
+
s.quadraticCurveTo(w, -z, w, -z + r);
|
|
14
|
+
s.lineTo(w, z - r);
|
|
15
|
+
s.quadraticCurveTo(w, z, w - r, z);
|
|
16
|
+
s.lineTo(-w + r, z);
|
|
17
|
+
s.quadraticCurveTo(-w, z, -w, z - r);
|
|
18
|
+
s.lineTo(-w, -z + r);
|
|
19
|
+
s.quadraticCurveTo(-w, -z, -w + r, -z);
|
|
20
|
+
g = new THREE.ExtrudeGeometry(s, { depth: d.height, bevelEnabled: false, steps: 1, curveSegments: 6 });
|
|
21
|
+
g.rotateX(Math.PI / 2);
|
|
22
|
+
g.translate(0, d.height / 2, 0);
|
|
23
|
+
}
|
|
24
|
+
else if (part.shape.provider === 'portal') {
|
|
25
|
+
if (!part.shape.profile?.length)
|
|
26
|
+
throw Error('Portal profile is missing');
|
|
27
|
+
const s = new THREE.Shape(part.shape.profile.map(p => new THREE.Vector2(p.x, p.y)));
|
|
28
|
+
g = new THREE.ExtrudeGeometry(s, { depth: d.depth, bevelEnabled: false, steps: 1 });
|
|
29
|
+
g.translate(0, 0, -d.depth / 2);
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
throw Error('Unsupported V3 provider: ' + part.shape.provider);
|
|
33
|
+
return g;
|
|
34
|
+
}
|
|
35
|
+
export function disposeV3Meshes(group) {
|
|
36
|
+
for (const child of [...group.children]) {
|
|
37
|
+
if (child instanceof THREE.Mesh) {
|
|
38
|
+
child.geometry.dispose();
|
|
39
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
40
|
+
materials.forEach(m => m.dispose());
|
|
41
|
+
}
|
|
42
|
+
group.remove(child);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Build first, then swap; failed rendering must not erase the previous scene. No old meshes survive a successful swap. */
|
|
46
|
+
export function replaceV3Meshes(group, parts, palette) {
|
|
47
|
+
const staged = new THREE.Group(), ids = new Set();
|
|
48
|
+
try {
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
if (ids.has(part.id))
|
|
51
|
+
throw Error('Duplicate V3 geometry ID: ' + part.id);
|
|
52
|
+
ids.add(part.id);
|
|
53
|
+
const color = palette[part.material.token];
|
|
54
|
+
if (!color || !/^#[0-9a-f]{6}$/i.test(color))
|
|
55
|
+
throw Error('Unresolved material token: ' + part.material.token);
|
|
56
|
+
const { r, t } = part.pose;
|
|
57
|
+
if (r.length !== 3 || r.some(row => row.length !== 3 || row.some(x => !Number.isFinite(x))) || t.length !== 3 || t.some(x => !Number.isFinite(x)))
|
|
58
|
+
throw Error('Invalid V3 pose: ' + part.id);
|
|
59
|
+
const emissive = part.material.emissive;
|
|
60
|
+
if (emissive?.on && !/^#[0-9a-f]{6}$/i.test(palette[emissive.token] ?? ''))
|
|
61
|
+
throw Error('Unresolved emissive token');
|
|
62
|
+
const g = geometry(part);
|
|
63
|
+
const material = new THREE.MeshStandardMaterial({ color, metalness: part.material.preset === 'metal' ? 0.8 : 0, roughness: part.material.preset === 'metal' ? 0.3 : 0.65, transparent: !!part.material.transparent, opacity: part.material.transparent ? 0.35 : 1, emissive: emissive?.on ? palette[emissive.token] : 0, emissiveIntensity: emissive?.on ? emissive.intensity : 0 });
|
|
64
|
+
const mesh = new THREE.Mesh(g, material);
|
|
65
|
+
mesh.name = part.id;
|
|
66
|
+
mesh.userData.figurePartId = part.id;
|
|
67
|
+
mesh.matrixAutoUpdate = false;
|
|
68
|
+
mesh.matrix.set(r[0][0], r[0][1], r[0][2], t[0], r[1][0], r[1][1], r[1][2], t[1], r[2][0], r[2][1], r[2][2], t[2], 0, 0, 0, 1);
|
|
69
|
+
staged.add(mesh);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
disposeV3Meshes(staged);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
disposeV3Meshes(group);
|
|
77
|
+
for (const mesh of [...staged.children])
|
|
78
|
+
group.add(mesh);
|
|
79
|
+
group.updateMatrixWorld(true);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=v3-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-renderer.js","sourceRoot":"","sources":["../../client/modeller/v3-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import * as THREE from 'three'\n\n/** Evaluated V3 boundary: graph values are resolved before reaching the renderer. */\nexport interface V3RenderPart {\n id: string\n shape: { provider: string; dimensions: Record<string, number>; profile?: Array<{x:number;y:number}> }\n pose: {r:number[][];t:number[]}\n material: {token:string;preset?:string;transparent?:boolean;emissive?:{token:string;intensity:number;on:boolean}}\n segments?:number\n}\n\nfunction geometry(part:V3RenderPart):THREE.BufferGeometry {\n const d=part.shape.dimensions\n let g:THREE.BufferGeometry\n if(part.shape.provider==='box')g=new THREE.BoxGeometry(d.width,d.height,d.depth)\n else if(part.shape.provider==='cylinder')g=new THREE.CylinderGeometry(d.radius,d.radius,d.length,part.segments??12)\n else if(part.shape.provider==='roundedBox'){\n const w=d.width/2,z=d.depth/2,r=d.round,s=new THREE.Shape()\n s.moveTo(-w+r,-z);s.lineTo(w-r,-z);s.quadraticCurveTo(w,-z,w,-z+r)\n s.lineTo(w,z-r);s.quadraticCurveTo(w,z,w-r,z);s.lineTo(-w+r,z)\n s.quadraticCurveTo(-w,z,-w,z-r);s.lineTo(-w,-z+r);s.quadraticCurveTo(-w,-z,-w+r,-z)\n g=new THREE.ExtrudeGeometry(s,{depth:d.height,bevelEnabled:false,steps:1,curveSegments:6})\n g.rotateX(Math.PI/2);g.translate(0,d.height/2,0)\n }else if(part.shape.provider==='portal'){\n if(!part.shape.profile?.length)throw Error('Portal profile is missing')\n const s=new THREE.Shape(part.shape.profile.map(p=>new THREE.Vector2(p.x,p.y)))\n g=new THREE.ExtrudeGeometry(s,{depth:d.depth,bevelEnabled:false,steps:1});g.translate(0,0,-d.depth/2)\n }else throw Error('Unsupported V3 provider: '+part.shape.provider)\n return g\n}\n\nexport function disposeV3Meshes(group:THREE.Group):void {\n for(const child of [...group.children]){\n if(child instanceof THREE.Mesh){child.geometry.dispose();const materials=Array.isArray(child.material)?child.material:[child.material];materials.forEach(m=>m.dispose())}\n group.remove(child)\n }\n}\n\n/** Build first, then swap; failed rendering must not erase the previous scene. No old meshes survive a successful swap. */\nexport function replaceV3Meshes(group:THREE.Group,parts:readonly V3RenderPart[],palette:Record<string,string>):void {\n const staged=new THREE.Group(),ids=new Set<string>()\n try{\n for(const part of parts){\n if(ids.has(part.id))throw Error('Duplicate V3 geometry ID: '+part.id);ids.add(part.id)\n const color=palette[part.material.token]\n if(!color||!/^#[0-9a-f]{6}$/i.test(color))throw Error('Unresolved material token: '+part.material.token)\n const {r,t}=part.pose\n if(r.length!==3||r.some(row=>row.length!==3||row.some(x=>!Number.isFinite(x)))||t.length!==3||t.some(x=>!Number.isFinite(x)))throw Error('Invalid V3 pose: '+part.id)\n const emissive=part.material.emissive\n if(emissive?.on&&!/^#[0-9a-f]{6}$/i.test(palette[emissive.token]??''))throw Error('Unresolved emissive token')\n const g=geometry(part)\n const material=new THREE.MeshStandardMaterial({color,metalness:part.material.preset==='metal'?0.8:0,roughness:part.material.preset==='metal'?0.3:0.65,transparent:!!part.material.transparent,opacity:part.material.transparent?0.35:1,emissive:emissive?.on?palette[emissive.token]:0,emissiveIntensity:emissive?.on?emissive.intensity:0})\n const mesh=new THREE.Mesh(g,material);mesh.name=part.id;mesh.userData.figurePartId=part.id\n mesh.matrixAutoUpdate=false;mesh.matrix.set(r[0][0],r[0][1],r[0][2],t[0],r[1][0],r[1][1],r[1][2],t[1],r[2][0],r[2][1],r[2][2],t[2],0,0,0,1)\n staged.add(mesh)\n }\n }catch(error){disposeV3Meshes(staged);throw error}\n disposeV3Meshes(group)\n for(const mesh of [...staged.children])group.add(mesh)\n group.updateMatrixWorld(true)\n}\n"]}
|
|
@@ -41,6 +41,7 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
41
41
|
* 미저장 표시(`dirty`)는 그대로 남으므로 데이터를 잃지는 않았다. 잃은 것은 **사유**다.
|
|
42
42
|
*/
|
|
43
43
|
private saveFailure;
|
|
44
|
+
private sourceLoadFailure;
|
|
44
45
|
private showSaveModal;
|
|
45
46
|
private saveModalType;
|
|
46
47
|
private saveModalName;
|
|
@@ -217,6 +218,12 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
217
218
|
* 그래도 저장은 저작자의 별도 행위다. 잘못 눌러도 DB 정본이나 발행판은 바뀌지 않는다.
|
|
218
219
|
*/
|
|
219
220
|
private applySizingFix;
|
|
221
|
+
/**
|
|
222
|
+
* The release gate may need several anchors to keep a connected assembly together. It has already
|
|
223
|
+
* measured the complete set. Apply it directly to this unsaved work copy instead of making the
|
|
224
|
+
* author accept a sequence of opaque mini-proposals.
|
|
225
|
+
*/
|
|
226
|
+
private applySizingFixPlan;
|
|
220
227
|
/**
|
|
221
228
|
* 고른 변경을 받아들인다.
|
|
222
229
|
*
|