@operato/scene-ops 10.14.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/README.md +30 -0
- package/dist/cjs/apply-model.js +328 -0
- package/dist/cjs/apply-model.js.map +1 -0
- package/dist/cjs/apply-scene.js +359 -0
- package/dist/cjs/apply-scene.js.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/model.js +11 -0
- package/dist/cjs/model.js.map +1 -0
- package/dist/cjs/ops.js +3 -0
- package/dist/cjs/ops.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/src/apply-model.d.ts +47 -0
- package/dist/src/apply-model.js +321 -0
- package/dist/src/apply-model.js.map +1 -0
- package/dist/src/apply-scene.d.ts +94 -0
- package/dist/src/apply-scene.js +351 -0
- package/dist/src/apply-scene.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +16 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/model.d.ts +44 -0
- package/dist/src/model.js +10 -0
- package/dist/src/model.js.map +1 -0
- package/dist/src/ops.d.ts +135 -0
- package/dist/src/ops.js +2 -0
- package/dist/src/ops.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +50 -0
- package/src/apply-model.ts +370 -0
- package/src/apply-scene.ts +397 -0
- package/src/index.ts +15 -0
- package/src/model.ts +46 -0
- package/src/ops.ts +92 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findSceneComponent = findSceneComponent;
|
|
4
|
+
exports.dispatchSceneEditOp = dispatchSceneEditOp;
|
|
5
|
+
exports.dispatchSceneAction = dispatchSceneAction;
|
|
6
|
+
exports.computeArrangePositions = computeArrangePositions;
|
|
7
|
+
exports.collectAllRefids = collectAllRefids;
|
|
8
|
+
exports.captureOldKeys = captureOldKeys;
|
|
9
|
+
const apply_model_js_1 = require("./apply-model.js");
|
|
10
|
+
const NOOP_RESULT = { applied: false, inverseOps: [] };
|
|
11
|
+
/**
|
|
12
|
+
* Find a component by refid, or by id when that is all the caller has.
|
|
13
|
+
*
|
|
14
|
+
* refid is issued by things-scene to everything in the scene; id is an optional string the
|
|
15
|
+
* author may have set. refid is tried first because it is the one that is always there.
|
|
16
|
+
*/
|
|
17
|
+
function findSceneComponent(scene, target) {
|
|
18
|
+
if (!scene)
|
|
19
|
+
return null;
|
|
20
|
+
if (typeof target.refid === 'number') {
|
|
21
|
+
const byRefid = scene.rootContainer?.refidIndexMap?.get(target.refid);
|
|
22
|
+
if (byRefid)
|
|
23
|
+
return byRefid;
|
|
24
|
+
}
|
|
25
|
+
if (typeof target.id === 'string' && target.id.length > 0) {
|
|
26
|
+
return scene.findById?.(target.id) ?? null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Carry out one edit operation on a live scene.
|
|
32
|
+
*
|
|
33
|
+
* `replace` is not taken here — swapping the whole model is the host's own path, because it
|
|
34
|
+
* has to decide what happens to the selection and the undo stack.
|
|
35
|
+
*/
|
|
36
|
+
function dispatchSceneEditOp(scene, op, ctx = {}) {
|
|
37
|
+
if (!scene || !op)
|
|
38
|
+
return NOOP_RESULT;
|
|
39
|
+
const normalize = ctx.normalize ?? ((c) => c);
|
|
40
|
+
switch (op.op) {
|
|
41
|
+
case 'add': {
|
|
42
|
+
const normalized = normalize(op.component);
|
|
43
|
+
/* The inverse needs the refid the scene is about to issue, so we diff around the add. */
|
|
44
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
45
|
+
scene.add(normalized, {});
|
|
46
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
47
|
+
const inverseOps = newRefids.map(refid => ({ op: 'remove', refid }));
|
|
48
|
+
return { applied: true, inverseOps };
|
|
49
|
+
}
|
|
50
|
+
case 'remove': {
|
|
51
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
52
|
+
if (!target || !target.parent)
|
|
53
|
+
return NOOP_RESULT;
|
|
54
|
+
const savedModel = JSON.parse(JSON.stringify(target.model));
|
|
55
|
+
const prevSelected = scene.selected ?? [];
|
|
56
|
+
scene.selected = [target];
|
|
57
|
+
scene.remove();
|
|
58
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
59
|
+
return { applied: true, inverseOps: [{ op: 'add', component: savedModel }] };
|
|
60
|
+
}
|
|
61
|
+
case 'modify': {
|
|
62
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
63
|
+
if (!target)
|
|
64
|
+
return NOOP_RESULT;
|
|
65
|
+
const oldValues = captureOldKeys(target.model, op.patch);
|
|
66
|
+
const merged = (0, apply_model_js_1.mergeComponent)(target.model, op.patch);
|
|
67
|
+
target.set(merged);
|
|
68
|
+
/* `set` does not push a snapshot by itself — ask the commander for one. */
|
|
69
|
+
scene.commander?.execute(null, false);
|
|
70
|
+
return {
|
|
71
|
+
applied: true,
|
|
72
|
+
inverseOps: [{ op: 'modify', refid: op.refid, patch: oldValues }]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
case 'modifyScene': {
|
|
76
|
+
const root = scene.root;
|
|
77
|
+
if (!root || typeof root.set !== 'function')
|
|
78
|
+
return NOOP_RESULT;
|
|
79
|
+
const cleanPatch = { ...(op.patch || {}) };
|
|
80
|
+
delete cleanPatch.components; /* children move by add/remove/modify */
|
|
81
|
+
const oldValues = captureOldKeys(root.model, cleanPatch);
|
|
82
|
+
const merged = (0, apply_model_js_1.mergeComponent)(root.model, cleanPatch);
|
|
83
|
+
root.set(merged);
|
|
84
|
+
scene.commander?.execute(null, false);
|
|
85
|
+
return { applied: true, inverseOps: [{ op: 'modifyScene', patch: oldValues }] };
|
|
86
|
+
}
|
|
87
|
+
case 'align': {
|
|
88
|
+
const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
89
|
+
if (targets.length < 2)
|
|
90
|
+
return NOOP_RESULT;
|
|
91
|
+
const beforeBounds = targets.map((c) => ({
|
|
92
|
+
refid: c.get('refid'),
|
|
93
|
+
left: c.get('left'),
|
|
94
|
+
top: c.get('top'),
|
|
95
|
+
width: c.get('width'),
|
|
96
|
+
height: c.get('height')
|
|
97
|
+
}));
|
|
98
|
+
const prevSelected = scene.selected ?? [];
|
|
99
|
+
scene.selected = targets;
|
|
100
|
+
scene.align(op.direction);
|
|
101
|
+
scene.selected = prevSelected;
|
|
102
|
+
/* Undo restores the coordinates we read, rather than trying to invert the alignment. */
|
|
103
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
104
|
+
op: 'modify',
|
|
105
|
+
refid: b.refid,
|
|
106
|
+
patch: { left: b.left, top: b.top, width: b.width, height: b.height }
|
|
107
|
+
}));
|
|
108
|
+
return { applied: true, inverseOps };
|
|
109
|
+
}
|
|
110
|
+
case 'distribute': {
|
|
111
|
+
const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
112
|
+
if (targets.length < 2)
|
|
113
|
+
return NOOP_RESULT;
|
|
114
|
+
const beforeBounds = targets.map((c) => ({
|
|
115
|
+
refid: c.get('refid'),
|
|
116
|
+
left: c.get('left'),
|
|
117
|
+
top: c.get('top')
|
|
118
|
+
}));
|
|
119
|
+
const prevSelected = scene.selected ?? [];
|
|
120
|
+
scene.selected = targets;
|
|
121
|
+
/* things-scene spells these in capitals. */
|
|
122
|
+
scene.distribute(op.axis === 'horizontal' ? 'HORIZONTAL' : 'VERTICAL');
|
|
123
|
+
scene.selected = prevSelected;
|
|
124
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
125
|
+
op: 'modify',
|
|
126
|
+
refid: b.refid,
|
|
127
|
+
patch: { left: b.left, top: b.top }
|
|
128
|
+
}));
|
|
129
|
+
return { applied: true, inverseOps };
|
|
130
|
+
}
|
|
131
|
+
case 'group': {
|
|
132
|
+
const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
133
|
+
if (targets.length < 2)
|
|
134
|
+
return NOOP_RESULT;
|
|
135
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
136
|
+
const prevSelected = scene.selected ?? [];
|
|
137
|
+
scene.selected = targets;
|
|
138
|
+
scene.group();
|
|
139
|
+
scene.selected = prevSelected;
|
|
140
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
141
|
+
const inverseOps = newRefids.map(refid => ({ op: 'ungroup', refid }));
|
|
142
|
+
return { applied: true, inverseOps };
|
|
143
|
+
}
|
|
144
|
+
case 'ungroup': {
|
|
145
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
146
|
+
if (!target)
|
|
147
|
+
return NOOP_RESULT;
|
|
148
|
+
const childRefids = [];
|
|
149
|
+
const children = target.components ?? [];
|
|
150
|
+
for (const child of children) {
|
|
151
|
+
const r = child.get?.('refid');
|
|
152
|
+
if (typeof r === 'number')
|
|
153
|
+
childRefids.push(r);
|
|
154
|
+
}
|
|
155
|
+
const prevSelected = scene.selected ?? [];
|
|
156
|
+
scene.selected = [target];
|
|
157
|
+
scene.ungroup();
|
|
158
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
159
|
+
const inverseOps = childRefids.length >= 2 ? [{ op: 'group', refids: childRefids }] : [];
|
|
160
|
+
return { applied: true, inverseOps };
|
|
161
|
+
}
|
|
162
|
+
case 'zorder': {
|
|
163
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
164
|
+
if (!target)
|
|
165
|
+
return NOOP_RESULT;
|
|
166
|
+
const prevSelected = scene.selected ?? [];
|
|
167
|
+
scene.selected = [target];
|
|
168
|
+
scene.zorder(op.direction);
|
|
169
|
+
scene.selected = prevSelected;
|
|
170
|
+
/*
|
|
171
|
+
* Best effort. forward/backward invert exactly; front/back do not — sending something
|
|
172
|
+
* to the front and then to the back does not put it back where it was.
|
|
173
|
+
*/
|
|
174
|
+
const opp = {
|
|
175
|
+
forward: 'backward',
|
|
176
|
+
backward: 'forward',
|
|
177
|
+
front: 'back',
|
|
178
|
+
back: 'front'
|
|
179
|
+
};
|
|
180
|
+
const dir = opp[op.direction];
|
|
181
|
+
const inverseOps = dir ? [{ op: 'zorder', refid: op.refid, direction: dir }] : [];
|
|
182
|
+
return { applied: true, inverseOps };
|
|
183
|
+
}
|
|
184
|
+
case 'arrange': {
|
|
185
|
+
/*
|
|
186
|
+
* things-scene has no native call for this, so the positions are computed here and
|
|
187
|
+
* written with `set`. Only left/top move; width and height are the author's.
|
|
188
|
+
*/
|
|
189
|
+
const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
190
|
+
if (targets.length < 2)
|
|
191
|
+
return NOOP_RESULT;
|
|
192
|
+
const beforePositions = targets.map((c) => ({
|
|
193
|
+
refid: c.get('refid'),
|
|
194
|
+
left: c.get('left'),
|
|
195
|
+
top: c.get('top')
|
|
196
|
+
}));
|
|
197
|
+
const sizes = targets.map((c) => ({
|
|
198
|
+
width: typeof c.get('width') === 'number' ? c.get('width') : 0,
|
|
199
|
+
height: typeof c.get('height') === 'number' ? c.get('height') : 0
|
|
200
|
+
}));
|
|
201
|
+
const positions = computeArrangePositions(op.layout, beforePositions, sizes);
|
|
202
|
+
for (let i = 0; i < targets.length; i++) {
|
|
203
|
+
const t = targets[i];
|
|
204
|
+
const pos = positions[i];
|
|
205
|
+
const merged = (0, apply_model_js_1.mergeComponent)(t.model, { left: pos.left, top: pos.top });
|
|
206
|
+
t.set(merged);
|
|
207
|
+
}
|
|
208
|
+
/* One snapshot for the whole arrangement — moving twelve things is one undo. */
|
|
209
|
+
scene.commander?.execute(null, false);
|
|
210
|
+
const inverseOps = beforePositions.map(b => ({
|
|
211
|
+
op: 'modify',
|
|
212
|
+
refid: b.refid,
|
|
213
|
+
patch: { left: b.left, top: b.top }
|
|
214
|
+
}));
|
|
215
|
+
return { applied: true, inverseOps };
|
|
216
|
+
}
|
|
217
|
+
case 'replace':
|
|
218
|
+
return NOOP_RESULT;
|
|
219
|
+
default:
|
|
220
|
+
return NOOP_RESULT;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Carry out one view action on a live scene.
|
|
225
|
+
*
|
|
226
|
+
* Returns false when it could not — an unknown action, a missing component. Nothing here
|
|
227
|
+
* touches the model, so nothing here enters the undo history.
|
|
228
|
+
*
|
|
229
|
+
* `setSceneMode` changes `scene.mode`; a host holding its own reactive copy re-reads it.
|
|
230
|
+
* things-scene spells the modes 1 for edit and 0 for view.
|
|
231
|
+
*/
|
|
232
|
+
function dispatchSceneAction(scene, action) {
|
|
233
|
+
if (!scene || !action)
|
|
234
|
+
return false;
|
|
235
|
+
switch (action.action) {
|
|
236
|
+
case 'selectComponents': {
|
|
237
|
+
const refids = Array.isArray(action.refids) ? action.refids : [];
|
|
238
|
+
scene.selected = refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
case 'centerToComponent': {
|
|
242
|
+
const target = findSceneComponent(scene, { refid: action.refid });
|
|
243
|
+
if (!target)
|
|
244
|
+
return false;
|
|
245
|
+
scene.centerTo(target, action.animated !== false);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
case 'fitToView': {
|
|
249
|
+
scene.fit(action.mode ?? 'fit');
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
case 'setSceneMode': {
|
|
253
|
+
scene.mode = action.mode === 'edit' ? 1 : 0;
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
case 'highlightComponents': {
|
|
257
|
+
/* things-scene's own call — it outlines in 2D and in 3D. */
|
|
258
|
+
const refids = Array.isArray(action.refids) ? action.refids : [];
|
|
259
|
+
const targets = refids.map(r => findSceneComponent(scene, { refid: r })).filter((c) => c);
|
|
260
|
+
if (typeof scene.highlightSearchResults === 'function') {
|
|
261
|
+
scene.highlightSearchResults(targets);
|
|
262
|
+
}
|
|
263
|
+
if (typeof scene.invalidate === 'function')
|
|
264
|
+
scene.invalidate();
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
default:
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Where each component goes for a grid, row or column arrangement.
|
|
273
|
+
*
|
|
274
|
+
* - Only left/top are produced; sizes are the author's and are left alone.
|
|
275
|
+
* - grid cells are as wide and as tall as the largest component, so components of
|
|
276
|
+
* different sizes do not overlap. Filled row by row.
|
|
277
|
+
* - row and column walk each component's own size plus the gap, and `align` decides the
|
|
278
|
+
* cross axis.
|
|
279
|
+
* - Without an anchor, the first component's current position is the origin, so the result
|
|
280
|
+
* starts where the user is already looking.
|
|
281
|
+
*/
|
|
282
|
+
function computeArrangePositions(layout, current, sizes) {
|
|
283
|
+
if (current.length === 0)
|
|
284
|
+
return [];
|
|
285
|
+
const anchor = layout.anchor ?? { left: current[0].left, top: current[0].top };
|
|
286
|
+
const gap = typeof layout.gap === 'number' ? layout.gap : 10;
|
|
287
|
+
if (layout.type === 'grid') {
|
|
288
|
+
const cols = Math.max(1, Math.floor(layout.cols));
|
|
289
|
+
const cellW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
290
|
+
const cellH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
291
|
+
return current.map((_, i) => {
|
|
292
|
+
const row = Math.floor(i / cols);
|
|
293
|
+
const col = i % cols;
|
|
294
|
+
return {
|
|
295
|
+
left: anchor.left + col * (cellW + gap),
|
|
296
|
+
top: anchor.top + row * (cellH + gap)
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (layout.type === 'row') {
|
|
301
|
+
const align = layout.align ?? 'start';
|
|
302
|
+
const maxH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
303
|
+
const out = [];
|
|
304
|
+
let cursor = anchor.left;
|
|
305
|
+
for (const s of sizes) {
|
|
306
|
+
let top = anchor.top;
|
|
307
|
+
if (align === 'center')
|
|
308
|
+
top = anchor.top + (maxH - s.height) / 2;
|
|
309
|
+
else if (align === 'end')
|
|
310
|
+
top = anchor.top + (maxH - s.height);
|
|
311
|
+
out.push({ left: cursor, top });
|
|
312
|
+
cursor += s.width + gap;
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
const align = layout.align ?? 'start';
|
|
317
|
+
const maxW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
318
|
+
const out = [];
|
|
319
|
+
let cursor = anchor.top;
|
|
320
|
+
for (const s of sizes) {
|
|
321
|
+
let left = anchor.left;
|
|
322
|
+
if (align === 'center')
|
|
323
|
+
left = anchor.left + (maxW - s.width) / 2;
|
|
324
|
+
else if (align === 'end')
|
|
325
|
+
left = anchor.left + (maxW - s.width);
|
|
326
|
+
out.push({ left, top: cursor });
|
|
327
|
+
cursor += s.height + gap;
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Every refid currently in the scene.
|
|
333
|
+
*
|
|
334
|
+
* Called on both sides of an `add` or a `group`, so that the difference tells us which refids
|
|
335
|
+
* the scene just issued — which is the only way to write their inverse.
|
|
336
|
+
*/
|
|
337
|
+
function collectAllRefids(scene) {
|
|
338
|
+
const refids = [];
|
|
339
|
+
const map = scene?.rootContainer?.refidIndexMap;
|
|
340
|
+
if (map && typeof map.forEach === 'function') {
|
|
341
|
+
map.forEach((_, refid) => refids.push(refid));
|
|
342
|
+
}
|
|
343
|
+
return refids;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The current values of exactly the keys a patch is about to change, deep-cloned.
|
|
347
|
+
*
|
|
348
|
+
* This is the patch of the inverse `modify`. A key the model did not have is kept as null,
|
|
349
|
+
* which the mergers read as "remove it" — so undoing an added key removes it again.
|
|
350
|
+
*/
|
|
351
|
+
function captureOldKeys(model, patch) {
|
|
352
|
+
const out = {};
|
|
353
|
+
for (const k of Object.keys(patch || {})) {
|
|
354
|
+
const v = model?.[k];
|
|
355
|
+
out[k] = v === undefined ? null : JSON.parse(JSON.stringify(v));
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=apply-scene.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-scene.js","sourceRoot":"","sources":["../../src/apply-scene.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Applying edit operations to a scene that is open on screen.\n *\n * The difference from `apply-model.ts` is the commander. Everything here goes through\n * things-scene's own mutation API (`scene.add`, `target.set`, `scene.align`, …), so the\n * commander records a snapshot and the user keeps undo, the dirty mark and their selection.\n * Rebuilding the model and handing it back would take all three away.\n *\n * The scene is typed `any` on purpose: things-scene's Scene is a large surface and this\n * module needs eight methods of it. Typing it loosely is what lets the whole file be tested\n * with a plain object, which is how the operations below are actually covered.\n */\nimport type { ArrangeLayout, SceneActionOp, SceneEditOp } from './ops.js'\nimport { mergeComponent } from './apply-model.js'\n\nexport interface DispatchContext {\n /**\n * Fill an `add` component out with the defaults of its type before it goes in.\n *\n * The host does this because the defaults live in its template registry — an editor knows\n * what a fresh component of each type looks like, and this module does not. Left out, the\n * component goes to the scene exactly as given.\n */\n normalize?: (c: any) => any\n}\n\nexport interface DispatchResult {\n /** False when nothing happened — an unknown refid, too few targets, an op we do not take. */\n applied: boolean\n /**\n * How to undo this one operation, computed from the scene as it was. One operation can\n * need several: an `align` of five components inverts to five `modify`s.\n */\n inverseOps: SceneEditOp[]\n}\n\nconst NOOP_RESULT: DispatchResult = { applied: false, inverseOps: [] }\n\n/**\n * Find a component by refid, or by id when that is all the caller has.\n *\n * refid is issued by things-scene to everything in the scene; id is an optional string the\n * author may have set. refid is tried first because it is the one that is always there.\n */\nexport function findSceneComponent(scene: any, target: { id?: string; refid?: number }): any {\n if (!scene) return null\n if (typeof target.refid === 'number') {\n const byRefid = scene.rootContainer?.refidIndexMap?.get(target.refid)\n if (byRefid) return byRefid\n }\n if (typeof target.id === 'string' && target.id.length > 0) {\n return scene.findById?.(target.id) ?? null\n }\n return null\n}\n\n/**\n * Carry out one edit operation on a live scene.\n *\n * `replace` is not taken here — swapping the whole model is the host's own path, because it\n * has to decide what happens to the selection and the undo stack.\n */\nexport function dispatchSceneEditOp(\n scene: any,\n op: SceneEditOp,\n ctx: DispatchContext = {}\n): DispatchResult {\n if (!scene || !op) return NOOP_RESULT\n const normalize = ctx.normalize ?? ((c: any) => c)\n\n switch (op.op) {\n case 'add': {\n const normalized = normalize(op.component)\n /* The inverse needs the refid the scene is about to issue, so we diff around the add. */\n const prevRefids = new Set<number>(collectAllRefids(scene))\n scene.add(normalized, {})\n const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r))\n const inverseOps: SceneEditOp[] = newRefids.map(refid => ({ op: 'remove', refid }))\n return { applied: true, inverseOps }\n }\n\n case 'remove': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target || !target.parent) return NOOP_RESULT\n const savedModel = JSON.parse(JSON.stringify(target.model))\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.remove()\n scene.selected = prevSelected.filter((c: any) => c !== target)\n return { applied: true, inverseOps: [{ op: 'add', component: savedModel }] }\n }\n\n case 'modify': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const oldValues = captureOldKeys(target.model, op.patch as any)\n const merged = mergeComponent(target.model, op.patch as any)\n target.set(merged)\n /* `set` does not push a snapshot by itself — ask the commander for one. */\n scene.commander?.execute(null, false)\n return {\n applied: true,\n inverseOps: [{ op: 'modify', refid: op.refid, patch: oldValues }]\n }\n }\n\n case 'modifyScene': {\n const root = scene.root\n if (!root || typeof root.set !== 'function') return NOOP_RESULT\n const cleanPatch = { ...((op.patch as any) || {}) }\n delete cleanPatch.components /* children move by add/remove/modify */\n const oldValues = captureOldKeys(root.model, cleanPatch)\n const merged = mergeComponent(root.model, cleanPatch)\n root.set(merged)\n scene.commander?.execute(null, false)\n return { applied: true, inverseOps: [{ op: 'modifyScene', patch: oldValues }] }\n }\n\n case 'align': {\n const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const beforeBounds = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top'),\n width: c.get('width'),\n height: c.get('height')\n }))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n scene.align(op.direction)\n scene.selected = prevSelected\n /* Undo restores the coordinates we read, rather than trying to invert the alignment. */\n const inverseOps: SceneEditOp[] = beforeBounds.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top, width: b.width, height: b.height } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'distribute': {\n const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const beforeBounds = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top')\n }))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n /* things-scene spells these in capitals. */\n scene.distribute(op.axis === 'horizontal' ? 'HORIZONTAL' : 'VERTICAL')\n scene.selected = prevSelected\n const inverseOps: SceneEditOp[] = beforeBounds.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'group': {\n const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const prevRefids = new Set<number>(collectAllRefids(scene))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n scene.group()\n scene.selected = prevSelected\n const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r))\n const inverseOps: SceneEditOp[] = newRefids.map(refid => ({ op: 'ungroup', refid }))\n return { applied: true, inverseOps }\n }\n\n case 'ungroup': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const childRefids: number[] = []\n const children = (target as any).components ?? []\n for (const child of children) {\n const r = child.get?.('refid')\n if (typeof r === 'number') childRefids.push(r)\n }\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.ungroup()\n scene.selected = prevSelected.filter((c: any) => c !== target)\n const inverseOps: SceneEditOp[] =\n childRefids.length >= 2 ? [{ op: 'group', refids: childRefids }] : []\n return { applied: true, inverseOps }\n }\n\n case 'zorder': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.zorder(op.direction)\n scene.selected = prevSelected\n /*\n * Best effort. forward/backward invert exactly; front/back do not — sending something\n * to the front and then to the back does not put it back where it was.\n */\n const opp: Record<string, 'front' | 'back' | 'forward' | 'backward'> = {\n forward: 'backward',\n backward: 'forward',\n front: 'back',\n back: 'front'\n }\n const dir = opp[op.direction]\n const inverseOps: SceneEditOp[] = dir ? [{ op: 'zorder', refid: op.refid, direction: dir }] : []\n return { applied: true, inverseOps }\n }\n\n case 'arrange': {\n /*\n * things-scene has no native call for this, so the positions are computed here and\n * written with `set`. Only left/top move; width and height are the author's.\n */\n const targets = op.refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n\n const beforePositions = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top')\n }))\n const sizes = targets.map((c: any) => ({\n width: typeof c.get('width') === 'number' ? c.get('width') : 0,\n height: typeof c.get('height') === 'number' ? c.get('height') : 0\n }))\n\n const positions = computeArrangePositions(op.layout, beforePositions, sizes)\n for (let i = 0; i < targets.length; i++) {\n const t = targets[i]\n const pos = positions[i]\n const merged = mergeComponent(t.model, { left: pos.left, top: pos.top } as any)\n t.set(merged)\n }\n /* One snapshot for the whole arrangement — moving twelve things is one undo. */\n scene.commander?.execute(null, false)\n\n const inverseOps: SceneEditOp[] = beforePositions.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'replace':\n return NOOP_RESULT\n\n default:\n return NOOP_RESULT\n }\n}\n\n/**\n * Carry out one view action on a live scene.\n *\n * Returns false when it could not — an unknown action, a missing component. Nothing here\n * touches the model, so nothing here enters the undo history.\n *\n * `setSceneMode` changes `scene.mode`; a host holding its own reactive copy re-reads it.\n * things-scene spells the modes 1 for edit and 0 for view.\n */\nexport function dispatchSceneAction(scene: any, action: SceneActionOp): boolean {\n if (!scene || !action) return false\n switch (action.action) {\n case 'selectComponents': {\n const refids = Array.isArray(action.refids) ? action.refids : []\n scene.selected = refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n return true\n }\n case 'centerToComponent': {\n const target = findSceneComponent(scene, { refid: action.refid })\n if (!target) return false\n scene.centerTo(target, action.animated !== false)\n return true\n }\n case 'fitToView': {\n scene.fit(action.mode ?? 'fit')\n return true\n }\n case 'setSceneMode': {\n scene.mode = action.mode === 'edit' ? 1 : 0\n return true\n }\n case 'highlightComponents': {\n /* things-scene's own call — it outlines in 2D and in 3D. */\n const refids = Array.isArray(action.refids) ? action.refids : []\n const targets = refids.map(r => findSceneComponent(scene, { refid: r })).filter((c: any) => c)\n if (typeof scene.highlightSearchResults === 'function') {\n scene.highlightSearchResults(targets)\n }\n if (typeof scene.invalidate === 'function') scene.invalidate()\n return true\n }\n default:\n return false\n }\n}\n\n/**\n * Where each component goes for a grid, row or column arrangement.\n *\n * - Only left/top are produced; sizes are the author's and are left alone.\n * - grid cells are as wide and as tall as the largest component, so components of\n * different sizes do not overlap. Filled row by row.\n * - row and column walk each component's own size plus the gap, and `align` decides the\n * cross axis.\n * - Without an anchor, the first component's current position is the origin, so the result\n * starts where the user is already looking.\n */\nexport function computeArrangePositions(\n layout: ArrangeLayout,\n current: Array<{ left: number; top: number }>,\n sizes: Array<{ width: number; height: number }>\n): Array<{ left: number; top: number }> {\n if (current.length === 0) return []\n const anchor = layout.anchor ?? { left: current[0].left, top: current[0].top }\n const gap = typeof layout.gap === 'number' ? layout.gap : 10\n\n if (layout.type === 'grid') {\n const cols = Math.max(1, Math.floor(layout.cols))\n const cellW = sizes.reduce((m, s) => Math.max(m, s.width), 0)\n const cellH = sizes.reduce((m, s) => Math.max(m, s.height), 0)\n return current.map((_, i) => {\n const row = Math.floor(i / cols)\n const col = i % cols\n return {\n left: anchor.left + col * (cellW + gap),\n top: anchor.top + row * (cellH + gap)\n }\n })\n }\n\n if (layout.type === 'row') {\n const align = layout.align ?? 'start'\n const maxH = sizes.reduce((m, s) => Math.max(m, s.height), 0)\n const out: Array<{ left: number; top: number }> = []\n let cursor = anchor.left\n for (const s of sizes) {\n let top = anchor.top\n if (align === 'center') top = anchor.top + (maxH - s.height) / 2\n else if (align === 'end') top = anchor.top + (maxH - s.height)\n out.push({ left: cursor, top })\n cursor += s.width + gap\n }\n return out\n }\n\n const align = layout.align ?? 'start'\n const maxW = sizes.reduce((m, s) => Math.max(m, s.width), 0)\n const out: Array<{ left: number; top: number }> = []\n let cursor = anchor.top\n for (const s of sizes) {\n let left = anchor.left\n if (align === 'center') left = anchor.left + (maxW - s.width) / 2\n else if (align === 'end') left = anchor.left + (maxW - s.width)\n out.push({ left, top: cursor })\n cursor += s.height + gap\n }\n return out\n}\n\n/**\n * Every refid currently in the scene.\n *\n * Called on both sides of an `add` or a `group`, so that the difference tells us which refids\n * the scene just issued — which is the only way to write their inverse.\n */\nexport function collectAllRefids(scene: any): number[] {\n const refids: number[] = []\n const map = scene?.rootContainer?.refidIndexMap\n if (map && typeof map.forEach === 'function') {\n map.forEach((_: any, refid: number) => refids.push(refid))\n }\n return refids\n}\n\n/**\n * The current values of exactly the keys a patch is about to change, deep-cloned.\n *\n * This is the patch of the inverse `modify`. A key the model did not have is kept as null,\n * which the mergers read as \"remove it\" — so undoing an added key removes it again.\n */\nexport function captureOldKeys(model: any, patch: any): any {\n const out: any = {}\n for (const k of Object.keys(patch || {})) {\n const v = model?.[k]\n out[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))\n }\n return out\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* @operato/scene-ops — describing a change to a scene, and carrying it out.
|
|
6
|
+
*
|
|
7
|
+
* A scene can be changed while it is stored (a JSON model in a file or a row) or while it is
|
|
8
|
+
* open (a things-scene Scene with a commander behind it). Those are two appliers, but they
|
|
9
|
+
* have to agree about what a change *is*, so the vocabulary and both appliers live here
|
|
10
|
+
* together.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here knows who proposed the change. A language model, a template, an importer and
|
|
13
|
+
* a person dragging a box all produce the same operations.
|
|
14
|
+
*/
|
|
15
|
+
tslib_1.__exportStar(require("./model.js"), exports);
|
|
16
|
+
tslib_1.__exportStar(require("./ops.js"), exports);
|
|
17
|
+
tslib_1.__exportStar(require("./apply-model.js"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./apply-scene.js"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;GAUG;AACH,qDAA0B;AAC1B,mDAAwB;AACxB,2DAAgC;AAChC,2DAAgC","sourcesContent":["/**\n * @operato/scene-ops — describing a change to a scene, and carrying it out.\n *\n * A scene can be changed while it is stored (a JSON model in a file or a row) or while it is\n * open (a things-scene Scene with a commander behind it). Those are two appliers, but they\n * have to agree about what a change *is*, so the vocabulary and both appliers live here\n * together.\n *\n * Nothing here knows who proposed the change. A language model, a template, an importer and\n * a person dragging a box all produce the same operations.\n */\nexport * from './model.js'\nexport * from './ops.js'\nexport * from './apply-model.js'\nexport * from './apply-scene.js'\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The scene model — the JSON a things-scene board/scene is stored and loaded as.
|
|
4
|
+
*
|
|
5
|
+
* This shape used to be declared in `@things-factory/board-import` as `BoardModel` /
|
|
6
|
+
* `BoardComponent`, inside a CAD-import pipeline stage. It is not an import concept and it
|
|
7
|
+
* is not a board concept: it is what a scene *is* on disk. Anything that reads or writes a
|
|
8
|
+
* scene needs it, so it lives here with the operations that change it.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/model.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG","sourcesContent":["/**\n * The scene model — the JSON a things-scene board/scene is stored and loaded as.\n *\n * This shape used to be declared in `@things-factory/board-import` as `BoardModel` /\n * `BoardComponent`, inside a CAD-import pipeline stage. It is not an import concept and it\n * is not a board concept: it is what a scene *is* on disk. Anything that reads or writes a\n * scene needs it, so it lives here with the operations that change it.\n */\n\n/** One component in a scene, and its children if it is a container. */\nexport interface SceneComponentModel {\n /** Domain type, e.g. 'rect', 'twin-resource-card'. */\n type: string\n left: number\n top: number\n width: number\n height: number\n rotation?: number\n /**\n * Assigned by things-scene when the component joins a scene, and stable while it stays\n * there. Every operation below targets components by this, never by `id` — `id` is\n * optional metadata that most components do not carry.\n */\n refid?: number\n id?: string\n /** Children, when this component is a group or container. */\n components?: SceneComponentModel[]\n /** Components carry their own properties; we do not enumerate them. */\n [k: string]: any\n}\n\n/**\n * A whole scene. The root is a component too — it has its own fillStyle, camera, lights and\n * so on — but it is reached by a separate operation (`modifyScene`) because it has no refid.\n */\nexport interface SceneModel {\n width?: number\n height?: number\n fillStyle?: string\n /**\n * Optional: an empty scene, a legacy file, or a model that is itself the root container\n * may not have it. Always read it as `model.components ?? []`.\n */\n components?: SceneComponentModel[]\n [k: string]: any\n}\n"]}
|
package/dist/cjs/ops.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops.js","sourceRoot":"","sources":["../../src/ops.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Scene edit operations — the vocabulary for describing a change to a scene.\n *\n * One vocabulary, two appliers: `apply-model.ts` changes the stored JSON, `apply-scene.ts`\n * changes a Scene that is open on screen. They live together so they cannot drift.\n *\n * ── Targeting ──\n * Existing components are addressed by `refid` only. things-scene issues one to every\n * component; `model.id` is optional metadata and is absent more often than not, so it cannot\n * be a target. There is one channel, not two.\n *\n * ── The root ──\n * The scene root is the top-level parent (things-scene's model-layer) and carries its own\n * properties: fillStyle, width, height, fitMode, translate, scale, sky, skyColor, exposure,\n * the hemi/dir light fields, the camera fields. It has no refid, so `modifyScene` reaches it\n * and `modify` reaches everything else.\n *\n * Note that a board's *name* is a column on the board row in the database, not a field of\n * the scene model. Sending it through `modifyScene` writes a dead key into the JSON and\n * changes nothing on screen.\n */\nimport type { SceneComponentModel, SceneModel } from './model.js'\n\nexport type AlignDirection = 'left' | 'right' | 'center' | 'top' | 'middle' | 'bottom'\n\nexport type DistributeAxis = 'horizontal' | 'vertical'\n\nexport type ZorderDirection = 'front' | 'back' | 'forward' | 'backward'\n\n/**\n * The layout an `arrange` operation asks for.\n *\n * This sits above align/distribute so that \"in a 3x2 grid\", \"in one row\", \"in a column\"\n * is one operation rather than a list of coordinates.\n *\n * Only left/top change — width and height are kept. Changing size is a separate `modify`.\n *\n * Positions are computed by the scene applier, because they need each component's current\n * width and height. The model applier treats `arrange` as a no-op.\n */\nexport type ArrangeLayout =\n | { type: 'grid'; cols: number; gap?: number; anchor?: { left: number; top: number } }\n | {\n type: 'row'\n gap?: number\n anchor?: { left: number; top: number }\n align?: 'start' | 'center' | 'end'\n }\n | {\n type: 'column'\n gap?: number\n anchor?: { left: number; top: number }\n align?: 'start' | 'center' | 'end'\n }\n\n/** An operation that changes the scene. */\nexport type SceneEditOp =\n | { op: 'add'; component: SceneComponentModel }\n | { op: 'remove'; refid: number }\n | { op: 'modify'; refid: number; patch: Partial<SceneComponentModel> }\n | { op: 'modifyScene'; patch: Partial<SceneModel> }\n | { op: 'replace'; model: SceneModel }\n | { op: 'align'; refids: number[]; direction: AlignDirection }\n | { op: 'distribute'; refids: number[]; axis: DistributeAxis }\n | { op: 'group'; refids: number[] }\n | { op: 'ungroup'; refid: number }\n | { op: 'zorder'; refid: number; direction: ZorderDirection }\n | { op: 'arrange'; refids: number[]; layout: ArrangeLayout }\n\n/**\n * An operation that changes what the viewer sees but not what the scene is.\n *\n * Separate from `SceneEditOp` on purpose: these leave the model alone, so they do not enter\n * the undo history and do not make the document dirty. A host that mixes the two ends up\n * asking the user to save because the AI scrolled the view.\n */\nexport type SceneActionOp =\n | { action: 'selectComponents'; refids: number[] }\n | { action: 'centerToComponent'; refid: number; animated?: boolean }\n | { action: 'fitToView'; mode?: 'fit' | 'ratio' | 'width' | 'height' }\n | { action: 'setSceneMode'; mode: 'edit' | 'view' }\n /** Outline several components at once — the \"these are all the matches\" of a search. */\n | { action: 'highlightComponents'; refids: number[] }\n\n/** A batch of edit operations, with whatever the proposer wants to say about them. */\nexport interface SceneEditPatch {\n ops: SceneEditOp[]\n /** One or two sentences, for the person who has to approve it. */\n summary: string\n /** 0..1 */\n confidence: number\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applying edit operations to a stored scene model.
|
|
3
|
+
*
|
|
4
|
+
* Pure: nothing here mutates its input. This is the applier for a scene that is not open —
|
|
5
|
+
* a file being transformed, a template being instantiated, a proposal being previewed. For a
|
|
6
|
+
* scene that is open on screen, use `apply-scene.ts`; things-scene's own API is the authority
|
|
7
|
+
* there and several operations are deliberately no-ops here.
|
|
8
|
+
*/
|
|
9
|
+
import type { SceneComponentModel, SceneModel } from './model.js';
|
|
10
|
+
import type { SceneEditOp, SceneEditPatch } from './ops.js';
|
|
11
|
+
export interface PatchApplyReport {
|
|
12
|
+
/** The scene after the patch. Unchanged input is returned as-is when every op was a no-op. */
|
|
13
|
+
model: SceneModel;
|
|
14
|
+
/** The ops that actually changed something. */
|
|
15
|
+
applied: SceneEditOp[];
|
|
16
|
+
/** The ops that did nothing — a refid that is not there, most often. */
|
|
17
|
+
missed: SceneEditOp[];
|
|
18
|
+
}
|
|
19
|
+
export declare function applyScenePatch(model: SceneModel | undefined, patch: SceneEditPatch): SceneModel;
|
|
20
|
+
/**
|
|
21
|
+
* The same, but reporting which ops landed.
|
|
22
|
+
*
|
|
23
|
+
* `modify` and `remove` do nothing at all when the refid is not in the scene. Without this
|
|
24
|
+
* report the host answers "done" to a change that never happened — which is exactly what a
|
|
25
|
+
* language model producing a wrong refid looks like from the user's seat.
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyScenePatchVerbose(model: SceneModel | undefined, patch: SceneEditPatch): PatchApplyReport;
|
|
28
|
+
/**
|
|
29
|
+
* The inverse of one operation, against the scene as it was before that operation ran.
|
|
30
|
+
*
|
|
31
|
+
* This is what makes undo possible for a proposed change: a host that applies ops one at a
|
|
32
|
+
* time and keeps the inverses can restore the scene by running them backwards.
|
|
33
|
+
*
|
|
34
|
+
* Returns null when the inverse cannot be known from the model alone. `add` is the clear
|
|
35
|
+
* case — its inverse needs the refid that things-scene issues on insert, so the host captures
|
|
36
|
+
* that itself. The scene-only operations are the same story: their result depends on
|
|
37
|
+
* coordinates and parenting that only the live scene knows.
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeInverseOp(model: SceneModel | undefined, op: SceneEditOp): SceneEditOp | null;
|
|
40
|
+
export declare function applyOp(model: SceneModel, op: SceneEditOp): SceneModel;
|
|
41
|
+
/**
|
|
42
|
+
* Apply a partial patch to one component.
|
|
43
|
+
*
|
|
44
|
+
* Nested objects such as `threeD` are merged rather than replaced, so that changing a colour
|
|
45
|
+
* does not take the geometry with it. A null value removes the key, as above.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mergeComponent(base: SceneComponentModel, patch: Partial<SceneComponentModel>): SceneComponentModel;
|