worldorbit 2.5.16 → 2.5.17
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 +1 -1
- package/dist/browser/core/dist/index.js +750 -73
- package/dist/browser/editor/dist/index.js +1303 -135
- package/dist/browser/markdown/dist/index.js +631 -72
- package/dist/browser/viewer/dist/index.js +658 -77
- package/dist/unpkg/core/dist/index.js +750 -73
- package/dist/unpkg/editor/dist/index.js +1303 -135
- package/dist/unpkg/markdown/dist/index.js +631 -72
- package/dist/unpkg/viewer/dist/index.js +658 -77
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +284 -202
- package/dist/unpkg/worldorbit-markdown.min.js +66 -58
- package/dist/unpkg/worldorbit-viewer.min.js +76 -68
- package/dist/unpkg/worldorbit.js +797 -78
- package/dist/unpkg/worldorbit.min.js +80 -72
- package/package.json +1 -1
- package/packages/core/dist/atlas-edit.js +74 -0
- package/packages/core/dist/atlas-validate.js +122 -8
- package/packages/core/dist/draft-parse.js +212 -8
- package/packages/core/dist/draft.d.ts +5 -2
- package/packages/core/dist/draft.js +59 -3
- package/packages/core/dist/format.js +63 -1
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +248 -46
- package/packages/core/dist/types.d.ts +41 -2
- package/packages/editor/dist/editor.js +597 -61
- package/packages/editor/dist/types.d.ts +3 -1
- package/packages/viewer/dist/atlas-state.js +6 -0
- package/packages/viewer/dist/atlas-viewer.js +1 -0
- package/packages/viewer/dist/render.js +31 -2
- package/packages/viewer/dist/theme.js +1 -0
- package/packages/viewer/dist/tooltip.js +9 -0
- package/packages/viewer/dist/types.d.ts +8 -1
- package/packages/viewer/dist/viewer.js +12 -1
|
@@ -541,6 +541,7 @@
|
|
|
541
541
|
system,
|
|
542
542
|
groups: [],
|
|
543
543
|
relations: [],
|
|
544
|
+
events: [],
|
|
544
545
|
objects
|
|
545
546
|
};
|
|
546
547
|
}
|
|
@@ -924,8 +925,10 @@
|
|
|
924
925
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
925
926
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
926
927
|
const systemId = document2.system?.id ?? null;
|
|
927
|
-
const
|
|
928
|
-
const
|
|
928
|
+
const activeEventId = options.activeEventId ?? null;
|
|
929
|
+
const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
|
|
930
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
931
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
929
932
|
const positions = /* @__PURE__ */ new Map();
|
|
930
933
|
const orbitDrafts = [];
|
|
931
934
|
const leaderDrafts = [];
|
|
@@ -934,7 +937,7 @@
|
|
|
934
937
|
const atObjects = [];
|
|
935
938
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
936
939
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
937
|
-
for (const object of
|
|
940
|
+
for (const object of effectiveObjects) {
|
|
938
941
|
const placement = object.placement;
|
|
939
942
|
if (!placement) {
|
|
940
943
|
rootObjects.push(object);
|
|
@@ -1029,13 +1032,14 @@
|
|
|
1029
1032
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1030
1033
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1031
1034
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1032
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1035
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1033
1036
|
const relations = createSceneRelations(document2, objects);
|
|
1034
|
-
const
|
|
1035
|
-
const
|
|
1037
|
+
const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
|
|
1038
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1039
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1036
1040
|
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1037
1041
|
const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
|
|
1038
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1042
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1039
1043
|
return {
|
|
1040
1044
|
width,
|
|
1041
1045
|
height,
|
|
@@ -1061,6 +1065,8 @@
|
|
|
1061
1065
|
groups,
|
|
1062
1066
|
semanticGroups,
|
|
1063
1067
|
viewpoints,
|
|
1068
|
+
events,
|
|
1069
|
+
activeEventId,
|
|
1064
1070
|
objects,
|
|
1065
1071
|
orbitVisuals,
|
|
1066
1072
|
relations,
|
|
@@ -1079,6 +1085,35 @@
|
|
|
1079
1085
|
y: center.y + dx * sin + dy * cos
|
|
1080
1086
|
};
|
|
1081
1087
|
}
|
|
1088
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1089
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1090
|
+
if (!activeEventId) {
|
|
1091
|
+
return cloned;
|
|
1092
|
+
}
|
|
1093
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1094
|
+
if (!activeEvent) {
|
|
1095
|
+
return cloned;
|
|
1096
|
+
}
|
|
1097
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1098
|
+
for (const pose of activeEvent.positions) {
|
|
1099
|
+
const object = objectMap.get(pose.objectId);
|
|
1100
|
+
if (!object) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
object.placement = pose.placement ? structuredClone(pose.placement) : null;
|
|
1104
|
+
if (pose.inner) {
|
|
1105
|
+
object.properties.inner = { ...pose.inner };
|
|
1106
|
+
} else {
|
|
1107
|
+
delete object.properties.inner;
|
|
1108
|
+
}
|
|
1109
|
+
if (pose.outer) {
|
|
1110
|
+
object.properties.outer = { ...pose.outer };
|
|
1111
|
+
} else {
|
|
1112
|
+
delete object.properties.outer;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return cloned;
|
|
1116
|
+
}
|
|
1082
1117
|
function resolveLayoutPreset(document2) {
|
|
1083
1118
|
const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1084
1119
|
switch (rawScale) {
|
|
@@ -1234,24 +1269,14 @@
|
|
|
1234
1269
|
hidden: draft.object.properties.hidden === true
|
|
1235
1270
|
};
|
|
1236
1271
|
}
|
|
1237
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1272
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1238
1273
|
const labels = [];
|
|
1239
1274
|
const occupied = [];
|
|
1240
|
-
const
|
|
1275
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1276
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1241
1277
|
for (const object of visibleObjects) {
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1245
|
-
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1246
|
-
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1247
|
-
let attempts = 0;
|
|
1248
|
-
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1249
|
-
labelY += direction * 14 * labelMultiplier;
|
|
1250
|
-
secondaryY += direction * 14 * labelMultiplier;
|
|
1251
|
-
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1252
|
-
attempts += 1;
|
|
1253
|
-
}
|
|
1254
|
-
occupied.push(bounds);
|
|
1278
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1279
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1255
1280
|
labels.push({
|
|
1256
1281
|
renderId: `${object.renderId}-label`,
|
|
1257
1282
|
objectId: object.objectId,
|
|
@@ -1260,17 +1285,128 @@
|
|
|
1260
1285
|
semanticGroupIds: [...object.semanticGroupIds],
|
|
1261
1286
|
label: object.label,
|
|
1262
1287
|
secondaryLabel: object.secondaryLabel,
|
|
1263
|
-
x:
|
|
1264
|
-
y: labelY,
|
|
1265
|
-
secondaryY,
|
|
1266
|
-
textAnchor:
|
|
1267
|
-
direction: direction
|
|
1288
|
+
x: placement.x,
|
|
1289
|
+
y: placement.labelY,
|
|
1290
|
+
secondaryY: placement.secondaryY,
|
|
1291
|
+
textAnchor: placement.textAnchor,
|
|
1292
|
+
direction: placement.direction,
|
|
1268
1293
|
hidden: object.hidden
|
|
1269
1294
|
});
|
|
1270
1295
|
}
|
|
1271
1296
|
return labels;
|
|
1272
1297
|
}
|
|
1273
|
-
function
|
|
1298
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1299
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1300
|
+
if (priorityDiff !== 0) {
|
|
1301
|
+
return priorityDiff;
|
|
1302
|
+
}
|
|
1303
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1304
|
+
if (renderPriorityDiff !== 0) {
|
|
1305
|
+
return renderPriorityDiff;
|
|
1306
|
+
}
|
|
1307
|
+
return left.sortKey - right.sortKey;
|
|
1308
|
+
}
|
|
1309
|
+
function labelPlacementPriority(object) {
|
|
1310
|
+
switch (object.object.type) {
|
|
1311
|
+
case "star":
|
|
1312
|
+
return 0;
|
|
1313
|
+
case "planet":
|
|
1314
|
+
return 1;
|
|
1315
|
+
case "moon":
|
|
1316
|
+
return 2;
|
|
1317
|
+
case "belt":
|
|
1318
|
+
case "ring":
|
|
1319
|
+
return 3;
|
|
1320
|
+
case "asteroid":
|
|
1321
|
+
case "comet":
|
|
1322
|
+
return 4;
|
|
1323
|
+
case "structure":
|
|
1324
|
+
case "phenomenon":
|
|
1325
|
+
return 5;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1329
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1330
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1331
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1332
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1333
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1334
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1335
|
+
return placement;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1342
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1343
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1344
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1345
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1346
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1347
|
+
const preferHorizontal = object.object.type === "structure" || object.object.type === "phenomenon" || object.object.placement?.mode === "at" || object.object.placement?.mode === "surface" || object.object.placement?.mode === "free";
|
|
1348
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1349
|
+
}
|
|
1350
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1351
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1352
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1353
|
+
}
|
|
1354
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1355
|
+
}
|
|
1356
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1357
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1358
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1359
|
+
}
|
|
1360
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1361
|
+
}
|
|
1362
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1363
|
+
const step = 14 * labelMultiplier;
|
|
1364
|
+
switch (direction) {
|
|
1365
|
+
case "above": {
|
|
1366
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1367
|
+
return {
|
|
1368
|
+
x: object.x,
|
|
1369
|
+
labelY,
|
|
1370
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1371
|
+
textAnchor: "middle",
|
|
1372
|
+
direction
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
case "below": {
|
|
1376
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1377
|
+
return {
|
|
1378
|
+
x: object.x,
|
|
1379
|
+
labelY,
|
|
1380
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1381
|
+
textAnchor: "middle",
|
|
1382
|
+
direction
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
case "left": {
|
|
1386
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1387
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1388
|
+
return {
|
|
1389
|
+
x,
|
|
1390
|
+
labelY,
|
|
1391
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1392
|
+
textAnchor: "end",
|
|
1393
|
+
direction
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
case "right": {
|
|
1397
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1398
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1399
|
+
return {
|
|
1400
|
+
x,
|
|
1401
|
+
labelY,
|
|
1402
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1403
|
+
textAnchor: "start",
|
|
1404
|
+
direction
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1274
1410
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1275
1411
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1276
1412
|
return [
|
|
@@ -1285,6 +1421,10 @@
|
|
|
1285
1421
|
id: "relations",
|
|
1286
1422
|
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1287
1423
|
},
|
|
1424
|
+
{
|
|
1425
|
+
id: "events",
|
|
1426
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1427
|
+
},
|
|
1288
1428
|
{
|
|
1289
1429
|
id: "objects",
|
|
1290
1430
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1296,7 +1436,7 @@
|
|
|
1296
1436
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1297
1437
|
];
|
|
1298
1438
|
}
|
|
1299
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1439
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1300
1440
|
const groups = /* @__PURE__ */ new Map();
|
|
1301
1441
|
const ensureGroup = (groupId) => {
|
|
1302
1442
|
if (!groupId) {
|
|
@@ -1345,7 +1485,7 @@
|
|
|
1345
1485
|
}
|
|
1346
1486
|
}
|
|
1347
1487
|
for (const group of groups.values()) {
|
|
1348
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1488
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1349
1489
|
}
|
|
1350
1490
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1351
1491
|
}
|
|
@@ -1379,6 +1519,29 @@
|
|
|
1379
1519
|
};
|
|
1380
1520
|
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1381
1521
|
}
|
|
1522
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1523
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1524
|
+
return events.map((event) => {
|
|
1525
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1526
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1527
|
+
...event.participantObjectIds
|
|
1528
|
+
])];
|
|
1529
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1530
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1531
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1532
|
+
return {
|
|
1533
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1534
|
+
eventId: event.id,
|
|
1535
|
+
event,
|
|
1536
|
+
objectIds,
|
|
1537
|
+
participantIds: [...event.participantObjectIds],
|
|
1538
|
+
targetObjectId: event.targetObjectId,
|
|
1539
|
+
x: centroidX,
|
|
1540
|
+
y: centroidY,
|
|
1541
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1542
|
+
};
|
|
1543
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1544
|
+
}
|
|
1382
1545
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1383
1546
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1384
1547
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1432,6 +1595,7 @@
|
|
|
1432
1595
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1433
1596
|
objectId: null,
|
|
1434
1597
|
selectedObjectId: null,
|
|
1598
|
+
eventIds: [],
|
|
1435
1599
|
projection,
|
|
1436
1600
|
preset,
|
|
1437
1601
|
rotationDeg: 0,
|
|
@@ -1468,6 +1632,9 @@
|
|
|
1468
1632
|
draft.select = normalizedValue;
|
|
1469
1633
|
}
|
|
1470
1634
|
return;
|
|
1635
|
+
case "events":
|
|
1636
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
1637
|
+
return;
|
|
1471
1638
|
case "projection":
|
|
1472
1639
|
case "view":
|
|
1473
1640
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1524,6 +1691,7 @@
|
|
|
1524
1691
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1525
1692
|
objectId,
|
|
1526
1693
|
selectedObjectId,
|
|
1694
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
1527
1695
|
projection: draft.projection ?? projection,
|
|
1528
1696
|
preset: draft.preset ?? preset,
|
|
1529
1697
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
@@ -1581,7 +1749,7 @@
|
|
|
1581
1749
|
next["orbits-front"] = enabled;
|
|
1582
1750
|
continue;
|
|
1583
1751
|
}
|
|
1584
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1752
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1585
1753
|
next[rawLayer] = enabled;
|
|
1586
1754
|
}
|
|
1587
1755
|
}
|
|
@@ -1629,7 +1797,7 @@
|
|
|
1629
1797
|
}
|
|
1630
1798
|
return parts.join(" - ");
|
|
1631
1799
|
}
|
|
1632
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
1800
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1633
1801
|
let minX = Number.POSITIVE_INFINITY;
|
|
1634
1802
|
let minY = Number.POSITIVE_INFINITY;
|
|
1635
1803
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1659,7 +1827,7 @@
|
|
|
1659
1827
|
for (const label of labels) {
|
|
1660
1828
|
if (label.hidden)
|
|
1661
1829
|
continue;
|
|
1662
|
-
includeLabelBounds(label, include);
|
|
1830
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1663
1831
|
}
|
|
1664
1832
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1665
1833
|
return createBounds(0, 0, width, height);
|
|
@@ -1697,13 +1865,10 @@
|
|
|
1697
1865
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1698
1866
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1699
1867
|
}
|
|
1700
|
-
function includeLabelBounds(label, include) {
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
include(
|
|
1704
|
-
include(label.x + labelHalfWidth, label.y + 8);
|
|
1705
|
-
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
1706
|
-
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
1868
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
1869
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
1870
|
+
include(bounds.left, bounds.top);
|
|
1871
|
+
include(bounds.right, bounds.bottom);
|
|
1707
1872
|
}
|
|
1708
1873
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1709
1874
|
if (positions.has(object.id)) {
|
|
@@ -2093,7 +2258,7 @@
|
|
|
2093
2258
|
return null;
|
|
2094
2259
|
}
|
|
2095
2260
|
}
|
|
2096
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2261
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2097
2262
|
let minX = Number.POSITIVE_INFINITY;
|
|
2098
2263
|
let minY = Number.POSITIVE_INFINITY;
|
|
2099
2264
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2122,7 +2287,7 @@
|
|
|
2122
2287
|
}
|
|
2123
2288
|
for (const label of labels) {
|
|
2124
2289
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2125
|
-
includeLabelBounds(label, include);
|
|
2290
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2126
2291
|
}
|
|
2127
2292
|
}
|
|
2128
2293
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2147,12 +2312,28 @@
|
|
|
2147
2312
|
}
|
|
2148
2313
|
return current.id;
|
|
2149
2314
|
}
|
|
2150
|
-
function createLabelRect(
|
|
2315
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2316
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2317
|
+
}
|
|
2318
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2319
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2320
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2321
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2322
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2323
|
+
let left = x - labelHalfWidth;
|
|
2324
|
+
let right = x + labelHalfWidth;
|
|
2325
|
+
if (textAnchor === "start") {
|
|
2326
|
+
left = x;
|
|
2327
|
+
right = x + labelWidth;
|
|
2328
|
+
} else if (textAnchor === "end") {
|
|
2329
|
+
left = x - labelWidth;
|
|
2330
|
+
right = x;
|
|
2331
|
+
}
|
|
2151
2332
|
return {
|
|
2152
|
-
left
|
|
2153
|
-
right
|
|
2154
|
-
top: Math.min(labelY, secondaryY) -
|
|
2155
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2333
|
+
left,
|
|
2334
|
+
right,
|
|
2335
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2336
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2156
2337
|
};
|
|
2157
2338
|
}
|
|
2158
2339
|
function rectsOverlap(left, right) {
|
|
@@ -2339,11 +2520,6 @@
|
|
|
2339
2520
|
function customColorFor(value) {
|
|
2340
2521
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2341
2522
|
}
|
|
2342
|
-
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2343
|
-
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2344
|
-
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2345
|
-
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2346
|
-
}
|
|
2347
2523
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2348
2524
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2349
2525
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2383,6 +2559,7 @@
|
|
|
2383
2559
|
system,
|
|
2384
2560
|
groups: structuredClone(document2.groups ?? []),
|
|
2385
2561
|
relations: structuredClone(document2.relations ?? []),
|
|
2562
|
+
events: structuredClone(document2.events ?? []),
|
|
2386
2563
|
objects: document2.objects.map(cloneWorldOrbitObject),
|
|
2387
2564
|
diagnostics
|
|
2388
2565
|
};
|
|
@@ -2390,7 +2567,7 @@
|
|
|
2390
2567
|
function upgradeDocumentToDraftV2(document2, options = {}) {
|
|
2391
2568
|
return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document2, options));
|
|
2392
2569
|
}
|
|
2393
|
-
function materializeAtlasDocument(document2) {
|
|
2570
|
+
function materializeAtlasDocument(document2, options = {}) {
|
|
2394
2571
|
const system = document2.system ? {
|
|
2395
2572
|
type: "system",
|
|
2396
2573
|
id: document2.system.id,
|
|
@@ -2401,6 +2578,8 @@
|
|
|
2401
2578
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2402
2579
|
info: materializeDraftSystemInfo(document2.system)
|
|
2403
2580
|
} : null;
|
|
2581
|
+
const objects = document2.objects.map(cloneWorldOrbitObject);
|
|
2582
|
+
applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
|
|
2404
2583
|
return {
|
|
2405
2584
|
format: "worldorbit",
|
|
2406
2585
|
version: "1.0",
|
|
@@ -2408,7 +2587,8 @@
|
|
|
2408
2587
|
system,
|
|
2409
2588
|
groups: structuredClone(document2.groups ?? []),
|
|
2410
2589
|
relations: structuredClone(document2.relations ?? []),
|
|
2411
|
-
|
|
2590
|
+
events: document2.events.map(cloneWorldOrbitEvent),
|
|
2591
|
+
objects
|
|
2412
2592
|
};
|
|
2413
2593
|
}
|
|
2414
2594
|
function createDraftSystem(document2, defaults, atlasMetadata, annotations, diagnostics, preset) {
|
|
@@ -2533,6 +2713,7 @@
|
|
|
2533
2713
|
summary: viewpoint.summary,
|
|
2534
2714
|
focusObjectId: viewpoint.objectId,
|
|
2535
2715
|
selectedObjectId: viewpoint.selectedObjectId,
|
|
2716
|
+
events: [...viewpoint.eventIds],
|
|
2536
2717
|
projection: viewpoint.projection,
|
|
2537
2718
|
preset: viewpoint.preset,
|
|
2538
2719
|
zoom: viewpoint.scale,
|
|
@@ -2565,6 +2746,52 @@
|
|
|
2565
2746
|
info: { ...object.info }
|
|
2566
2747
|
};
|
|
2567
2748
|
}
|
|
2749
|
+
function cloneWorldOrbitEvent(event) {
|
|
2750
|
+
return {
|
|
2751
|
+
...event,
|
|
2752
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
2753
|
+
tags: [...event.tags],
|
|
2754
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
2758
|
+
return {
|
|
2759
|
+
objectId: pose.objectId,
|
|
2760
|
+
placement: clonePlacement(pose.placement),
|
|
2761
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
2762
|
+
outer: pose.outer ? { ...pose.outer } : void 0
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
function clonePlacement(placement) {
|
|
2766
|
+
return placement ? structuredClone(placement) : null;
|
|
2767
|
+
}
|
|
2768
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
2769
|
+
if (!activeEventId) {
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
2773
|
+
if (!event) {
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
2777
|
+
for (const pose of event.positions) {
|
|
2778
|
+
const object = objectMap.get(pose.objectId);
|
|
2779
|
+
if (!object) {
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
object.placement = clonePlacement(pose.placement);
|
|
2783
|
+
if (pose.inner) {
|
|
2784
|
+
object.properties.inner = { ...pose.inner };
|
|
2785
|
+
} else {
|
|
2786
|
+
delete object.properties.inner;
|
|
2787
|
+
}
|
|
2788
|
+
if (pose.outer) {
|
|
2789
|
+
object.properties.outer = { ...pose.outer };
|
|
2790
|
+
} else {
|
|
2791
|
+
delete object.properties.outer;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2568
2795
|
function cloneProperties(properties) {
|
|
2569
2796
|
const next = {};
|
|
2570
2797
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2662,6 +2889,9 @@
|
|
|
2662
2889
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2663
2890
|
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2664
2891
|
}
|
|
2892
|
+
if (viewpoint.events.length > 0) {
|
|
2893
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
2894
|
+
}
|
|
2665
2895
|
}
|
|
2666
2896
|
for (const annotation of system.annotations) {
|
|
2667
2897
|
const prefix = `annotation.${annotation.id}`;
|
|
@@ -2686,7 +2916,7 @@
|
|
|
2686
2916
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2687
2917
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2688
2918
|
}
|
|
2689
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
2919
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2690
2920
|
if (layers[key] !== void 0) {
|
|
2691
2921
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2692
2922
|
}
|
|
@@ -2790,6 +3020,10 @@
|
|
|
2790
3020
|
lines.push("");
|
|
2791
3021
|
lines.push(...formatAtlasRelation(relation));
|
|
2792
3022
|
}
|
|
3023
|
+
for (const event of [...document2.events].sort(compareIdLike)) {
|
|
3024
|
+
lines.push("");
|
|
3025
|
+
lines.push(...formatAtlasEvent(event));
|
|
3026
|
+
}
|
|
2793
3027
|
const sortedObjects = [...document2.objects].sort(compareObjects);
|
|
2794
3028
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2795
3029
|
lines.push("");
|
|
@@ -2820,6 +3054,10 @@
|
|
|
2820
3054
|
lines.push("");
|
|
2821
3055
|
lines.push(...formatAtlasRelation(relation));
|
|
2822
3056
|
}
|
|
3057
|
+
for (const event of [...legacy.events].sort(compareIdLike)) {
|
|
3058
|
+
lines.push("");
|
|
3059
|
+
lines.push(...formatAtlasEvent(event));
|
|
3060
|
+
}
|
|
2823
3061
|
const sortedObjects = [...legacy.objects].sort(compareObjects);
|
|
2824
3062
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2825
3063
|
lines.push("");
|
|
@@ -3031,6 +3269,9 @@
|
|
|
3031
3269
|
if (layerTokens.length > 0) {
|
|
3032
3270
|
lines.push(` layers ${layerTokens.join(" ")}`);
|
|
3033
3271
|
}
|
|
3272
|
+
if (viewpoint.events.length > 0) {
|
|
3273
|
+
lines.push(` events ${viewpoint.events.join(" ")}`);
|
|
3274
|
+
}
|
|
3034
3275
|
if (viewpoint.filter) {
|
|
3035
3276
|
lines.push(" filter");
|
|
3036
3277
|
if (viewpoint.filter.query) {
|
|
@@ -3103,6 +3344,54 @@
|
|
|
3103
3344
|
}
|
|
3104
3345
|
return lines;
|
|
3105
3346
|
}
|
|
3347
|
+
function formatAtlasEvent(event) {
|
|
3348
|
+
const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
|
|
3349
|
+
if (event.label) {
|
|
3350
|
+
lines.push(` label ${quoteIfNeeded(event.label)}`);
|
|
3351
|
+
}
|
|
3352
|
+
if (event.summary) {
|
|
3353
|
+
lines.push(` summary ${quoteIfNeeded(event.summary)}`);
|
|
3354
|
+
}
|
|
3355
|
+
if (event.targetObjectId) {
|
|
3356
|
+
lines.push(` target ${event.targetObjectId}`);
|
|
3357
|
+
}
|
|
3358
|
+
if (event.participantObjectIds.length > 0) {
|
|
3359
|
+
lines.push(` participants ${event.participantObjectIds.join(" ")}`);
|
|
3360
|
+
}
|
|
3361
|
+
if (event.timing) {
|
|
3362
|
+
lines.push(` timing ${quoteIfNeeded(event.timing)}`);
|
|
3363
|
+
}
|
|
3364
|
+
if (event.visibility) {
|
|
3365
|
+
lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
|
|
3366
|
+
}
|
|
3367
|
+
if (event.tags.length > 0) {
|
|
3368
|
+
lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
|
|
3369
|
+
}
|
|
3370
|
+
if (event.color) {
|
|
3371
|
+
lines.push(` color ${quoteIfNeeded(event.color)}`);
|
|
3372
|
+
}
|
|
3373
|
+
if (event.hidden) {
|
|
3374
|
+
lines.push(" hidden true");
|
|
3375
|
+
}
|
|
3376
|
+
if (event.positions.length > 0) {
|
|
3377
|
+
lines.push("");
|
|
3378
|
+
lines.push(" positions");
|
|
3379
|
+
for (const pose of [...event.positions].sort(comparePoseObjectId)) {
|
|
3380
|
+
lines.push(` pose ${pose.objectId}`);
|
|
3381
|
+
for (const fieldLine of formatEventPoseFields(pose)) {
|
|
3382
|
+
lines.push(` ${fieldLine}`);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
return lines;
|
|
3387
|
+
}
|
|
3388
|
+
function formatEventPoseFields(pose) {
|
|
3389
|
+
return [
|
|
3390
|
+
...formatPlacement(pose.placement),
|
|
3391
|
+
...formatOptionalUnit("inner", pose.inner),
|
|
3392
|
+
...formatOptionalUnit("outer", pose.outer)
|
|
3393
|
+
];
|
|
3394
|
+
}
|
|
3106
3395
|
function formatValue(value) {
|
|
3107
3396
|
if (Array.isArray(value)) {
|
|
3108
3397
|
return value.map((item) => quoteIfNeeded(item)).join(" ");
|
|
@@ -3144,7 +3433,7 @@
|
|
|
3144
3433
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
3145
3434
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
3146
3435
|
}
|
|
3147
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3436
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
3148
3437
|
if (layers[key] !== void 0) {
|
|
3149
3438
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
3150
3439
|
}
|
|
@@ -3172,6 +3461,9 @@
|
|
|
3172
3461
|
function compareIdLike(left, right) {
|
|
3173
3462
|
return left.id.localeCompare(right.id);
|
|
3174
3463
|
}
|
|
3464
|
+
function comparePoseObjectId(left, right) {
|
|
3465
|
+
return left.objectId.localeCompare(right.objectId);
|
|
3466
|
+
}
|
|
3175
3467
|
function objectTypeIndex(objectType) {
|
|
3176
3468
|
switch (objectType) {
|
|
3177
3469
|
case "star":
|
|
@@ -3367,6 +3659,7 @@
|
|
|
3367
3659
|
const diagnostics = [];
|
|
3368
3660
|
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
3369
3661
|
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3662
|
+
const eventIds = new Set(document2.events.map((event) => event.id));
|
|
3370
3663
|
if (!document2.system) {
|
|
3371
3664
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3372
3665
|
}
|
|
@@ -3376,6 +3669,7 @@
|
|
|
3376
3669
|
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3377
3670
|
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3378
3671
|
["relation", document2.relations.map((relation) => relation.id)],
|
|
3672
|
+
["event", document2.events.map((event) => event.id)],
|
|
3379
3673
|
["object", document2.objects.map((object) => object.id)]
|
|
3380
3674
|
]) {
|
|
3381
3675
|
for (const id of ids) {
|
|
@@ -3391,11 +3685,14 @@
|
|
|
3391
3685
|
validateRelation(relation, objectMap, diagnostics);
|
|
3392
3686
|
}
|
|
3393
3687
|
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
3394
|
-
|
|
3688
|
+
validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
|
|
3395
3689
|
}
|
|
3396
3690
|
for (const object of document2.objects) {
|
|
3397
3691
|
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
3398
3692
|
}
|
|
3693
|
+
for (const event of document2.events) {
|
|
3694
|
+
validateEvent(event, objectMap, diagnostics);
|
|
3695
|
+
}
|
|
3399
3696
|
return diagnostics;
|
|
3400
3697
|
}
|
|
3401
3698
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -3413,13 +3710,19 @@
|
|
|
3413
3710
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3414
3711
|
}
|
|
3415
3712
|
}
|
|
3416
|
-
function
|
|
3417
|
-
if (
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3713
|
+
function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
|
|
3714
|
+
if (sourceSchemaVersion === "2.1") {
|
|
3715
|
+
if (filter) {
|
|
3716
|
+
for (const groupId of filter.groupIds) {
|
|
3717
|
+
if (!groupIds.has(groupId)) {
|
|
3718
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
for (const eventId of eventRefs) {
|
|
3723
|
+
if (!eventIds.has(eventId)) {
|
|
3724
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
|
|
3725
|
+
}
|
|
3423
3726
|
}
|
|
3424
3727
|
}
|
|
3425
3728
|
}
|
|
@@ -3505,6 +3808,103 @@
|
|
|
3505
3808
|
}
|
|
3506
3809
|
}
|
|
3507
3810
|
}
|
|
3811
|
+
function validateEvent(event, objectMap, diagnostics) {
|
|
3812
|
+
const fieldPrefix = `event.${event.id}`;
|
|
3813
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
3814
|
+
if (!event.kind.trim()) {
|
|
3815
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
3816
|
+
}
|
|
3817
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
3818
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
3819
|
+
}
|
|
3820
|
+
if (event.targetObjectId) {
|
|
3821
|
+
referencedIds.add(event.targetObjectId);
|
|
3822
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
3823
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
3827
|
+
for (const participantId of event.participantObjectIds) {
|
|
3828
|
+
referencedIds.add(participantId);
|
|
3829
|
+
if (seenParticipants.has(participantId)) {
|
|
3830
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
3831
|
+
continue;
|
|
3832
|
+
}
|
|
3833
|
+
seenParticipants.add(participantId);
|
|
3834
|
+
if (!objectMap.has(participantId)) {
|
|
3835
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
3839
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
3840
|
+
}
|
|
3841
|
+
if (event.positions.length === 0) {
|
|
3842
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
3843
|
+
}
|
|
3844
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
3845
|
+
diagnostics.push(warn("validate.event.kind.participants", `Event "${event.id}" looks like an eclipse or transit but references fewer than three bodies.`, void 0, `${fieldPrefix}.participants`));
|
|
3846
|
+
}
|
|
3847
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
3848
|
+
for (const pose of event.positions) {
|
|
3849
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
3850
|
+
if (poseIds.has(pose.objectId)) {
|
|
3851
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
poseIds.add(pose.objectId);
|
|
3855
|
+
const object = objectMap.get(pose.objectId);
|
|
3856
|
+
if (!object) {
|
|
3857
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
3858
|
+
continue;
|
|
3859
|
+
}
|
|
3860
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
3861
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
3862
|
+
}
|
|
3863
|
+
validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
3867
|
+
const placement = pose.placement;
|
|
3868
|
+
if (!placement) {
|
|
3869
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
if (placement.mode === "orbit") {
|
|
3873
|
+
if (!objectMap.has(placement.target)) {
|
|
3874
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
3875
|
+
}
|
|
3876
|
+
if (placement.distance && placement.semiMajor) {
|
|
3877
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
3878
|
+
}
|
|
3879
|
+
return;
|
|
3880
|
+
}
|
|
3881
|
+
if (placement.mode === "surface") {
|
|
3882
|
+
const target = objectMap.get(placement.target);
|
|
3883
|
+
if (!target) {
|
|
3884
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
3885
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3886
|
+
diagnostics.push(error("validate.event.pose.surface.target.invalid", `Event surface target "${placement.target}" on "${eventId}:${pose.objectId}" is not surface-capable.`, void 0, `${fieldPrefix}.surface`));
|
|
3887
|
+
}
|
|
3888
|
+
return;
|
|
3889
|
+
}
|
|
3890
|
+
if (placement.mode === "at") {
|
|
3891
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3892
|
+
diagnostics.push(error("validate.event.pose.at.objectType", `Only structures and phenomena may use "at" placement in events; found "${object.type}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3893
|
+
}
|
|
3894
|
+
const reference = placement.reference;
|
|
3895
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
3896
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3897
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
3898
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3899
|
+
} else if (reference.kind === "lagrange") {
|
|
3900
|
+
if (!objectMap.has(reference.primary)) {
|
|
3901
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3902
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3903
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3508
3908
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3509
3909
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3510
3910
|
if (!reference) {
|
|
@@ -3705,6 +4105,21 @@
|
|
|
3705
4105
|
});
|
|
3706
4106
|
}
|
|
3707
4107
|
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
4108
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4109
|
+
"orbit",
|
|
4110
|
+
"distance",
|
|
4111
|
+
"semiMajor",
|
|
4112
|
+
"eccentricity",
|
|
4113
|
+
"period",
|
|
4114
|
+
"angle",
|
|
4115
|
+
"inclination",
|
|
4116
|
+
"phase",
|
|
4117
|
+
"at",
|
|
4118
|
+
"surface",
|
|
4119
|
+
"free",
|
|
4120
|
+
"inner",
|
|
4121
|
+
"outer"
|
|
4122
|
+
]);
|
|
3708
4123
|
function parseWorldOrbitAtlas(source) {
|
|
3709
4124
|
return parseAtlasSource(source);
|
|
3710
4125
|
}
|
|
@@ -3719,12 +4134,15 @@
|
|
|
3719
4134
|
const objectNodes = [];
|
|
3720
4135
|
const groups = [];
|
|
3721
4136
|
const relations = [];
|
|
4137
|
+
const events = [];
|
|
4138
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3722
4139
|
let sawDefaults = false;
|
|
3723
4140
|
let sawAtlas = false;
|
|
3724
4141
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3725
4142
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3726
4143
|
const groupIds = /* @__PURE__ */ new Set();
|
|
3727
4144
|
const relationIds = /* @__PURE__ */ new Set();
|
|
4145
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3728
4146
|
for (let index = 0; index < lines.length; index++) {
|
|
3729
4147
|
const rawLine = lines[index];
|
|
3730
4148
|
const lineNumber = index + 1;
|
|
@@ -3755,7 +4173,7 @@
|
|
|
3755
4173
|
continue;
|
|
3756
4174
|
}
|
|
3757
4175
|
if (indent === 0) {
|
|
3758
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
4176
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3759
4177
|
if (section.kind === "system") {
|
|
3760
4178
|
system = section.system;
|
|
3761
4179
|
} else if (section.kind === "defaults") {
|
|
@@ -3774,6 +4192,7 @@
|
|
|
3774
4192
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3775
4193
|
}
|
|
3776
4194
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
4195
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3777
4196
|
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3778
4197
|
const baseDocument = {
|
|
3779
4198
|
format: "worldorbit",
|
|
@@ -3781,6 +4200,7 @@
|
|
|
3781
4200
|
system,
|
|
3782
4201
|
groups,
|
|
3783
4202
|
relations,
|
|
4203
|
+
events: normalizedEvents,
|
|
3784
4204
|
objects,
|
|
3785
4205
|
diagnostics
|
|
3786
4206
|
};
|
|
@@ -3816,7 +4236,7 @@
|
|
|
3816
4236
|
const version = tokens[1].value.toLowerCase();
|
|
3817
4237
|
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3818
4238
|
}
|
|
3819
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
4239
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3820
4240
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
3821
4241
|
switch (keyword) {
|
|
3822
4242
|
case "system":
|
|
@@ -3853,7 +4273,7 @@
|
|
|
3853
4273
|
if (!system) {
|
|
3854
4274
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3855
4275
|
}
|
|
3856
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
4276
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3857
4277
|
case "annotation":
|
|
3858
4278
|
if (!system) {
|
|
3859
4279
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -3865,6 +4285,9 @@
|
|
|
3865
4285
|
case "relation":
|
|
3866
4286
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3867
4287
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
4288
|
+
case "event":
|
|
4289
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
4290
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3868
4291
|
case "object":
|
|
3869
4292
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3870
4293
|
default:
|
|
@@ -3901,7 +4324,7 @@
|
|
|
3901
4324
|
seenFields: /* @__PURE__ */ new Set()
|
|
3902
4325
|
};
|
|
3903
4326
|
}
|
|
3904
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
4327
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
3905
4328
|
if (tokens.length !== 2) {
|
|
3906
4329
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3907
4330
|
}
|
|
@@ -3918,6 +4341,7 @@
|
|
|
3918
4341
|
summary: "",
|
|
3919
4342
|
focusObjectId: null,
|
|
3920
4343
|
selectedObjectId: null,
|
|
4344
|
+
events: [],
|
|
3921
4345
|
projection: system.defaults.view,
|
|
3922
4346
|
preset: system.defaults.preset,
|
|
3923
4347
|
zoom: null,
|
|
@@ -3930,6 +4354,8 @@
|
|
|
3930
4354
|
return {
|
|
3931
4355
|
kind: "viewpoint",
|
|
3932
4356
|
viewpoint,
|
|
4357
|
+
sourceSchemaVersion,
|
|
4358
|
+
diagnostics,
|
|
3933
4359
|
seenFields: /* @__PURE__ */ new Set(),
|
|
3934
4360
|
inFilter: false,
|
|
3935
4361
|
filterIndent: null,
|
|
@@ -4020,6 +4446,49 @@
|
|
|
4020
4446
|
seenFields: /* @__PURE__ */ new Set()
|
|
4021
4447
|
};
|
|
4022
4448
|
}
|
|
4449
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
4450
|
+
if (tokens.length !== 2) {
|
|
4451
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
4452
|
+
}
|
|
4453
|
+
const id = normalizeIdentifier2(tokens[1].value);
|
|
4454
|
+
if (!id) {
|
|
4455
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
4456
|
+
}
|
|
4457
|
+
if (eventIds.has(id)) {
|
|
4458
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
4459
|
+
}
|
|
4460
|
+
const event = {
|
|
4461
|
+
id,
|
|
4462
|
+
kind: "",
|
|
4463
|
+
label: humanizeIdentifier3(id),
|
|
4464
|
+
summary: null,
|
|
4465
|
+
targetObjectId: null,
|
|
4466
|
+
participantObjectIds: [],
|
|
4467
|
+
timing: null,
|
|
4468
|
+
visibility: null,
|
|
4469
|
+
tags: [],
|
|
4470
|
+
color: null,
|
|
4471
|
+
hidden: false,
|
|
4472
|
+
positions: []
|
|
4473
|
+
};
|
|
4474
|
+
const rawPoses = [];
|
|
4475
|
+
events.push(event);
|
|
4476
|
+
eventPoseNodes.set(id, rawPoses);
|
|
4477
|
+
eventIds.add(id);
|
|
4478
|
+
return {
|
|
4479
|
+
kind: "event",
|
|
4480
|
+
event,
|
|
4481
|
+
sourceSchemaVersion,
|
|
4482
|
+
diagnostics,
|
|
4483
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
4484
|
+
rawPoses,
|
|
4485
|
+
inPositions: false,
|
|
4486
|
+
positionsIndent: null,
|
|
4487
|
+
activePose: null,
|
|
4488
|
+
poseIndent: null,
|
|
4489
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4023
4492
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
4024
4493
|
if (tokens.length < 3) {
|
|
4025
4494
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -4076,6 +4545,9 @@
|
|
|
4076
4545
|
case "relation":
|
|
4077
4546
|
applyRelationField(section, tokens, line);
|
|
4078
4547
|
return;
|
|
4548
|
+
case "event":
|
|
4549
|
+
applyEventField(section, indent, tokens, line);
|
|
4550
|
+
return;
|
|
4079
4551
|
case "object":
|
|
4080
4552
|
applyObjectField(section, indent, tokens, line);
|
|
4081
4553
|
return;
|
|
@@ -4202,7 +4674,14 @@
|
|
|
4202
4674
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
4203
4675
|
return;
|
|
4204
4676
|
case "layers":
|
|
4205
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
4677
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
4678
|
+
return;
|
|
4679
|
+
case "events":
|
|
4680
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
4681
|
+
line,
|
|
4682
|
+
column: tokens[0].column
|
|
4683
|
+
});
|
|
4684
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
4206
4685
|
return;
|
|
4207
4686
|
default:
|
|
4208
4687
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
@@ -4307,6 +4786,106 @@
|
|
|
4307
4786
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4308
4787
|
}
|
|
4309
4788
|
}
|
|
4789
|
+
function applyEventField(section, indent, tokens, line) {
|
|
4790
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
4791
|
+
section.activePose = null;
|
|
4792
|
+
section.poseIndent = null;
|
|
4793
|
+
section.activePoseSeenFields.clear();
|
|
4794
|
+
}
|
|
4795
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
4796
|
+
section.inPositions = false;
|
|
4797
|
+
section.positionsIndent = null;
|
|
4798
|
+
}
|
|
4799
|
+
if (section.activePose) {
|
|
4800
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
4801
|
+
return;
|
|
4802
|
+
}
|
|
4803
|
+
if (section.inPositions) {
|
|
4804
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
4805
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
4806
|
+
}
|
|
4807
|
+
const objectId = tokens[1].value;
|
|
4808
|
+
if (!objectId.trim()) {
|
|
4809
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
4810
|
+
}
|
|
4811
|
+
const rawPose = {
|
|
4812
|
+
objectId,
|
|
4813
|
+
fields: [],
|
|
4814
|
+
location: { line, column: tokens[0].column }
|
|
4815
|
+
};
|
|
4816
|
+
section.rawPoses.push(rawPose);
|
|
4817
|
+
section.activePose = rawPose;
|
|
4818
|
+
section.poseIndent = indent;
|
|
4819
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
4820
|
+
return;
|
|
4821
|
+
}
|
|
4822
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
4823
|
+
if (section.seenFields.has("positions")) {
|
|
4824
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
4825
|
+
}
|
|
4826
|
+
section.seenFields.add("positions");
|
|
4827
|
+
section.inPositions = true;
|
|
4828
|
+
section.positionsIndent = indent;
|
|
4829
|
+
return;
|
|
4830
|
+
}
|
|
4831
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4832
|
+
switch (key) {
|
|
4833
|
+
case "kind":
|
|
4834
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
4835
|
+
return;
|
|
4836
|
+
case "label":
|
|
4837
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
4838
|
+
return;
|
|
4839
|
+
case "summary":
|
|
4840
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
4841
|
+
return;
|
|
4842
|
+
case "target":
|
|
4843
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
4844
|
+
return;
|
|
4845
|
+
case "participants":
|
|
4846
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
4847
|
+
return;
|
|
4848
|
+
case "timing":
|
|
4849
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
4850
|
+
return;
|
|
4851
|
+
case "visibility":
|
|
4852
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
4853
|
+
return;
|
|
4854
|
+
case "tags":
|
|
4855
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4856
|
+
return;
|
|
4857
|
+
case "color":
|
|
4858
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
4859
|
+
return;
|
|
4860
|
+
case "hidden":
|
|
4861
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4862
|
+
line,
|
|
4863
|
+
column: tokens[0].column
|
|
4864
|
+
});
|
|
4865
|
+
return;
|
|
4866
|
+
default:
|
|
4867
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
4871
|
+
if (tokens.length < 2) {
|
|
4872
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
4873
|
+
}
|
|
4874
|
+
const key = tokens[0].value;
|
|
4875
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
4876
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
4877
|
+
}
|
|
4878
|
+
if (seenFields.has(key)) {
|
|
4879
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
4880
|
+
}
|
|
4881
|
+
seenFields.add(key);
|
|
4882
|
+
return {
|
|
4883
|
+
type: "field",
|
|
4884
|
+
key,
|
|
4885
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
4886
|
+
location: { line, column: tokens[0].column }
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4310
4889
|
function applyObjectField(section, indent, tokens, line) {
|
|
4311
4890
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
4312
4891
|
section.activeBlock = null;
|
|
@@ -4365,7 +4944,7 @@
|
|
|
4365
4944
|
function parseObjectTypeTokens(tokens, line) {
|
|
4366
4945
|
return parseTokenList(tokens, line, "objectTypes").filter((value) => value === "star" || value === "planet" || value === "moon" || value === "belt" || value === "asteroid" || value === "comet" || value === "ring" || value === "structure" || value === "phenomenon");
|
|
4367
4946
|
}
|
|
4368
|
-
function parseLayerTokens(tokens, line) {
|
|
4947
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
4369
4948
|
const layers = {};
|
|
4370
4949
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4371
4950
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -4375,7 +4954,13 @@
|
|
|
4375
4954
|
layers["orbits-front"] = enabled;
|
|
4376
4955
|
continue;
|
|
4377
4956
|
}
|
|
4378
|
-
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4957
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4958
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
4959
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
4960
|
+
line,
|
|
4961
|
+
column: tokens[0]?.column ?? 1
|
|
4962
|
+
});
|
|
4963
|
+
}
|
|
4379
4964
|
layers[raw] = enabled;
|
|
4380
4965
|
}
|
|
4381
4966
|
}
|
|
@@ -4514,7 +5099,7 @@
|
|
|
4514
5099
|
}
|
|
4515
5100
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4516
5101
|
const fieldMap = collectDraftFields(node.fields);
|
|
4517
|
-
const placement =
|
|
5102
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4518
5103
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4519
5104
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4520
5105
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -4566,6 +5151,24 @@
|
|
|
4566
5151
|
}
|
|
4567
5152
|
return object;
|
|
4568
5153
|
}
|
|
5154
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
5155
|
+
return {
|
|
5156
|
+
...event,
|
|
5157
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
5158
|
+
tags: [...new Set(event.tags)],
|
|
5159
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
5160
|
+
};
|
|
5161
|
+
}
|
|
5162
|
+
function normalizeDraftEventPose(rawPose) {
|
|
5163
|
+
const fieldMap = collectDraftFields(rawPose.fields);
|
|
5164
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
5165
|
+
return {
|
|
5166
|
+
objectId: rawPose.objectId,
|
|
5167
|
+
placement,
|
|
5168
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
5169
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
4569
5172
|
function collectDraftFields(fields) {
|
|
4570
5173
|
const grouped = /* @__PURE__ */ new Map();
|
|
4571
5174
|
for (const field of fields) {
|
|
@@ -4582,7 +5185,7 @@
|
|
|
4582
5185
|
}
|
|
4583
5186
|
return grouped;
|
|
4584
5187
|
}
|
|
4585
|
-
function
|
|
5188
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
4586
5189
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4587
5190
|
const atField = fieldMap.get("at")?.[0];
|
|
4588
5191
|
const surfaceField = fieldMap.get("surface")?.[0];
|
|
@@ -4875,6 +5478,7 @@
|
|
|
4875
5478
|
},
|
|
4876
5479
|
groups: [],
|
|
4877
5480
|
relations: [],
|
|
5481
|
+
events: [],
|
|
4878
5482
|
objects: [],
|
|
4879
5483
|
diagnostics: []
|
|
4880
5484
|
};
|
|
@@ -4892,6 +5496,10 @@
|
|
|
4892
5496
|
return path.key ? document2.system?.atlasMetadata[path.key] ?? null : null;
|
|
4893
5497
|
case "group":
|
|
4894
5498
|
return path.id ? findGroup(document2, path.id) : null;
|
|
5499
|
+
case "event":
|
|
5500
|
+
return path.id ? findEvent(document2, path.id) : null;
|
|
5501
|
+
case "event-pose":
|
|
5502
|
+
return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
|
|
4895
5503
|
case "object":
|
|
4896
5504
|
return path.id ? findObject(document2, path.id) : null;
|
|
4897
5505
|
case "viewpoint":
|
|
@@ -4921,6 +5529,19 @@
|
|
|
4921
5529
|
next.groups = next.groups.filter((group) => group.id !== path.id);
|
|
4922
5530
|
}
|
|
4923
5531
|
return next;
|
|
5532
|
+
case "event":
|
|
5533
|
+
if (path.id) {
|
|
5534
|
+
next.events = next.events.filter((event) => event.id !== path.id);
|
|
5535
|
+
}
|
|
5536
|
+
return next;
|
|
5537
|
+
case "event-pose":
|
|
5538
|
+
if (path.id && path.key) {
|
|
5539
|
+
const event = findEvent(next, path.id);
|
|
5540
|
+
if (event) {
|
|
5541
|
+
event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
return next;
|
|
4924
5545
|
case "viewpoint":
|
|
4925
5546
|
if (path.id) {
|
|
4926
5547
|
system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
|
|
@@ -4989,6 +5610,22 @@
|
|
|
4989
5610
|
};
|
|
4990
5611
|
}
|
|
4991
5612
|
}
|
|
5613
|
+
if (diagnostic.field?.startsWith("event.")) {
|
|
5614
|
+
const parts = diagnostic.field.split(".");
|
|
5615
|
+
if (parts[1] && findEvent(document2, parts[1])) {
|
|
5616
|
+
if (parts[2] === "pose" && parts[3] && findEventPose(document2, parts[1], parts[3])) {
|
|
5617
|
+
return {
|
|
5618
|
+
kind: "event-pose",
|
|
5619
|
+
id: parts[1],
|
|
5620
|
+
key: parts[3]
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
return {
|
|
5624
|
+
kind: "event",
|
|
5625
|
+
id: parts[1]
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
4992
5629
|
if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
|
|
4993
5630
|
return {
|
|
4994
5631
|
kind: "metadata",
|
|
@@ -5020,6 +5657,12 @@
|
|
|
5020
5657
|
function findRelation(document2, relationId) {
|
|
5021
5658
|
return document2.relations.find((relation) => relation.id === relationId) ?? null;
|
|
5022
5659
|
}
|
|
5660
|
+
function findEvent(document2, eventId) {
|
|
5661
|
+
return document2.events.find((event) => event.id === eventId) ?? null;
|
|
5662
|
+
}
|
|
5663
|
+
function findEventPose(document2, eventId, objectId) {
|
|
5664
|
+
return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
|
|
5665
|
+
}
|
|
5023
5666
|
function findViewpoint(system, viewpointId) {
|
|
5024
5667
|
return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
|
|
5025
5668
|
}
|
|
@@ -5197,6 +5840,7 @@
|
|
|
5197
5840
|
background: true,
|
|
5198
5841
|
guides: true,
|
|
5199
5842
|
relations: true,
|
|
5843
|
+
events: true,
|
|
5200
5844
|
orbits: true,
|
|
5201
5845
|
objects: true,
|
|
5202
5846
|
labels: true,
|
|
@@ -5345,12 +5989,14 @@
|
|
|
5345
5989
|
return {
|
|
5346
5990
|
version: "2.0",
|
|
5347
5991
|
viewpointId,
|
|
5992
|
+
activeEventId: renderOptions.activeEventId ?? null,
|
|
5348
5993
|
viewerState: { ...viewerState },
|
|
5349
5994
|
renderOptions: {
|
|
5350
5995
|
preset: renderOptions.preset,
|
|
5351
5996
|
projection: renderOptions.projection,
|
|
5352
5997
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
5353
|
-
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
|
|
5998
|
+
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
5999
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
5354
6000
|
},
|
|
5355
6001
|
filter: normalizeViewerFilter(filter)
|
|
5356
6002
|
};
|
|
@@ -5363,6 +6009,7 @@
|
|
|
5363
6009
|
return {
|
|
5364
6010
|
version: "2.0",
|
|
5365
6011
|
viewpointId: raw.viewpointId ?? null,
|
|
6012
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
|
|
5366
6013
|
viewerState: {
|
|
5367
6014
|
scale: raw.viewerState?.scale ?? 1,
|
|
5368
6015
|
rotationDeg: raw.viewerState?.rotationDeg ?? 0,
|
|
@@ -5374,7 +6021,8 @@
|
|
|
5374
6021
|
preset: raw.renderOptions?.preset,
|
|
5375
6022
|
projection: raw.renderOptions?.projection,
|
|
5376
6023
|
layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
|
|
5377
|
-
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
|
|
6024
|
+
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
|
|
6025
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
|
|
5378
6026
|
},
|
|
5379
6027
|
filter: normalizeViewerFilter(raw.filter ?? null)
|
|
5380
6028
|
};
|
|
@@ -5390,7 +6038,8 @@
|
|
|
5390
6038
|
renderOptions: {
|
|
5391
6039
|
...atlasState.renderOptions,
|
|
5392
6040
|
layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
|
|
5393
|
-
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
|
|
6041
|
+
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
|
|
6042
|
+
activeEventId: atlasState.renderOptions.activeEventId ?? null
|
|
5394
6043
|
},
|
|
5395
6044
|
filter: atlasState.filter ? { ...atlasState.filter } : null
|
|
5396
6045
|
}
|
|
@@ -5408,6 +6057,7 @@
|
|
|
5408
6057
|
background: viewpoint.layers.background,
|
|
5409
6058
|
guides: viewpoint.layers.guides,
|
|
5410
6059
|
relations: viewpoint.layers.relations,
|
|
6060
|
+
events: viewpoint.layers.events,
|
|
5411
6061
|
orbits: viewpoint.layers["orbits-front"] === void 0 && viewpoint.layers["orbits-back"] === void 0 ? void 0 : viewpoint.layers["orbits-front"] !== false || viewpoint.layers["orbits-back"] !== false,
|
|
5412
6062
|
objects: viewpoint.layers.objects,
|
|
5413
6063
|
labels: viewpoint.layers.labels,
|
|
@@ -5694,6 +6344,7 @@
|
|
|
5694
6344
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
5695
6345
|
const leaderMarkup = layers.guides ? scene.leaders.filter((leader) => !leader.hidden).filter((leader) => visibleObjectIds.has(leader.objectId)).filter((leader) => layers.structures || !isStructureLike(leader.object)).map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`).join("") : "";
|
|
5696
6346
|
const relationMarkup = layers.relations ? scene.relations.filter((relation) => !relation.hidden).filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId)).map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`).join("") : "";
|
|
6347
|
+
const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
|
|
5697
6348
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
5698
6349
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
5699
6350
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -5729,6 +6380,9 @@
|
|
|
5729
6380
|
.wo-orbit-front { opacity: 0.9; }
|
|
5730
6381
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
5731
6382
|
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
6383
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
6384
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
6385
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
5732
6386
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
5733
6387
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
5734
6388
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -5763,6 +6417,7 @@
|
|
|
5763
6417
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
5764
6418
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
5765
6419
|
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
6420
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
5766
6421
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
5767
6422
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
5768
6423
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -5770,6 +6425,20 @@
|
|
|
5770
6425
|
</g>
|
|
5771
6426
|
</g>
|
|
5772
6427
|
</svg>`;
|
|
6428
|
+
}
|
|
6429
|
+
function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
6430
|
+
const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
|
|
6431
|
+
if (participants.length === 0) {
|
|
6432
|
+
return "";
|
|
6433
|
+
}
|
|
6434
|
+
const stroke = event.event.color || theme.accent;
|
|
6435
|
+
const label = event.event.label || event.event.id;
|
|
6436
|
+
const lineMarkup = participants.map((object) => `<line class="wo-event-line" x1="${event.x}" y1="${event.y}" x2="${object.x}" y2="${object.y}" stroke="${escapeAttribute(stroke)}" data-event-id="${escapeAttribute(event.eventId)}" data-object-id="${escapeAttribute(object.objectId)}" />`).join("");
|
|
6437
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
6438
|
+
${lineMarkup}
|
|
6439
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
6440
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
6441
|
+
</g>`;
|
|
5773
6442
|
}
|
|
5774
6443
|
function renderOrbitLayer(scene, visibleObjectIds, includeStructures) {
|
|
5775
6444
|
const backParts = [];
|
|
@@ -6352,6 +7021,13 @@
|
|
|
6352
7021
|
value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
|
|
6353
7022
|
});
|
|
6354
7023
|
}
|
|
7024
|
+
if (details.relatedEvents.length > 0) {
|
|
7025
|
+
fields.set("events", {
|
|
7026
|
+
key: "events",
|
|
7027
|
+
label: "Events",
|
|
7028
|
+
value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
|
|
7029
|
+
});
|
|
7030
|
+
}
|
|
6355
7031
|
if (placement?.mode === "at") {
|
|
6356
7032
|
fields.set("placement", {
|
|
6357
7033
|
key: "placement",
|
|
@@ -6766,6 +7442,12 @@
|
|
|
6766
7442
|
emitAtlasStateChange();
|
|
6767
7443
|
return true;
|
|
6768
7444
|
},
|
|
7445
|
+
getActiveEventId() {
|
|
7446
|
+
return renderOptions.activeEventId ?? null;
|
|
7447
|
+
},
|
|
7448
|
+
setActiveEvent(id) {
|
|
7449
|
+
api.setRenderOptions({ activeEventId: id });
|
|
7450
|
+
},
|
|
6769
7451
|
search(query, limit = 12) {
|
|
6770
7452
|
return searchSceneObjects(scene, query, limit);
|
|
6771
7453
|
},
|
|
@@ -7030,6 +7712,7 @@
|
|
|
7030
7712
|
orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
|
|
7031
7713
|
relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
|
|
7032
7714
|
relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
|
|
7715
|
+
relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
|
|
7033
7716
|
parent: getObjectById(renderObject.parentId),
|
|
7034
7717
|
children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
|
|
7035
7718
|
ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
|
|
@@ -7346,7 +8029,8 @@
|
|
|
7346
8029
|
filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
|
|
7347
8030
|
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
7348
8031
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
7349
|
-
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
|
|
8032
|
+
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
|
|
8033
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
7350
8034
|
};
|
|
7351
8035
|
}
|
|
7352
8036
|
function mergeRenderOptions(current, next) {
|
|
@@ -7366,7 +8050,7 @@
|
|
|
7366
8050
|
};
|
|
7367
8051
|
}
|
|
7368
8052
|
function hasSceneAffectingRenderOptions(options) {
|
|
7369
|
-
return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.scaleModel !== void 0;
|
|
8053
|
+
return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
|
|
7370
8054
|
}
|
|
7371
8055
|
function resolveSourceRenderOptions(loaded, renderOptions) {
|
|
7372
8056
|
const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
|
|
@@ -7699,6 +8383,34 @@
|
|
|
7699
8383
|
description: "Rotates the saved camera angle in degrees.",
|
|
7700
8384
|
references: ["90deg = quarter turn", "180deg = flip"]
|
|
7701
8385
|
},
|
|
8386
|
+
"viewpoint-events": {
|
|
8387
|
+
description: "Lists event IDs that this viewpoint should feature in its detail panel.",
|
|
8388
|
+
references: ["solar-eclipse-naar", "transit-window conjunction"]
|
|
8389
|
+
},
|
|
8390
|
+
"event-kind": {
|
|
8391
|
+
description: "Short semantic event type for tooling and viewer overlays.",
|
|
8392
|
+
references: ["solar-eclipse", "lunar-eclipse", "transit"]
|
|
8393
|
+
},
|
|
8394
|
+
"event-target": {
|
|
8395
|
+
description: "Primary object this event is centered on.",
|
|
8396
|
+
references: ["Naar", "Seyra"]
|
|
8397
|
+
},
|
|
8398
|
+
"event-participants": {
|
|
8399
|
+
description: "Objects that participate in the event snapshot or description.",
|
|
8400
|
+
references: ["Iyath Naar Seyra", "Naar Seyra Orun"]
|
|
8401
|
+
},
|
|
8402
|
+
"event-timing": {
|
|
8403
|
+
description: "Free-text timing note for the event.",
|
|
8404
|
+
references: ['"Every late bloom season"', '"At local midyear"']
|
|
8405
|
+
},
|
|
8406
|
+
"event-visibility": {
|
|
8407
|
+
description: "Notes where or how the event is visible.",
|
|
8408
|
+
references: ['"Visible from Naar"', '"Southern hemisphere only"']
|
|
8409
|
+
},
|
|
8410
|
+
"event-viewpoints": {
|
|
8411
|
+
description: "Viewpoint IDs that should list this event prominently.",
|
|
8412
|
+
references: ["naar-system", "overview inner-system"]
|
|
8413
|
+
},
|
|
7702
8414
|
"placement-target": {
|
|
7703
8415
|
description: "Names the body or reference this object is attached to.",
|
|
7704
8416
|
references: ["orbit Primary", "surface Homeworld", "at Naar:L4"]
|
|
@@ -7817,12 +8529,23 @@
|
|
|
7817
8529
|
minimap: true,
|
|
7818
8530
|
tooltipMode: "hover",
|
|
7819
8531
|
onSelectionChange(selectedObject) {
|
|
8532
|
+
const activeEventId = selection ? selectionEventId(selection) : null;
|
|
7820
8533
|
if (ignoreViewerSelection || !selectedObject) {
|
|
7821
|
-
if (!ignoreViewerSelection
|
|
7822
|
-
|
|
8534
|
+
if (!ignoreViewerSelection) {
|
|
8535
|
+
if (selection?.kind === "event-pose" && selection.id) {
|
|
8536
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
8537
|
+
} else if (selection?.kind === "object") {
|
|
8538
|
+
setSelection(activeEventId ? { kind: "event", id: activeEventId } : null, false, true);
|
|
8539
|
+
} else if (selection?.kind === "event" && selection.id) {
|
|
8540
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
8541
|
+
}
|
|
7823
8542
|
}
|
|
7824
8543
|
return;
|
|
7825
8544
|
}
|
|
8545
|
+
if (activeEventId && findEventPose2(atlasDocument, activeEventId, selectedObject.objectId)) {
|
|
8546
|
+
setSelection({ kind: "event-pose", id: activeEventId, key: selectedObject.objectId }, false, true);
|
|
8547
|
+
return;
|
|
8548
|
+
}
|
|
7826
8549
|
setSelection({ kind: "object", id: selectedObject.objectId }, false, true);
|
|
7827
8550
|
},
|
|
7828
8551
|
onViewChange() {
|
|
@@ -7832,6 +8555,7 @@
|
|
|
7832
8555
|
toolbar.addEventListener("click", handleToolbarClick);
|
|
7833
8556
|
outline.addEventListener("click", handleOutlineClick);
|
|
7834
8557
|
overlay.addEventListener("pointerdown", handleOverlayPointerDown);
|
|
8558
|
+
inspector?.addEventListener("click", handleInspectorClick);
|
|
7835
8559
|
inspector?.addEventListener("input", handleInspectorInput);
|
|
7836
8560
|
inspector?.addEventListener("change", handleInspectorChange);
|
|
7837
8561
|
sourcePane?.addEventListener("input", handleSourceInput);
|
|
@@ -7906,6 +8630,28 @@
|
|
|
7906
8630
|
replaceAtlasDocument(nextDocument, true, { kind: "object", id });
|
|
7907
8631
|
return id;
|
|
7908
8632
|
},
|
|
8633
|
+
addEvent() {
|
|
8634
|
+
const id = createUniqueId("event", atlasDocument.events.map((event) => event.id));
|
|
8635
|
+
const created = {
|
|
8636
|
+
id,
|
|
8637
|
+
kind: "",
|
|
8638
|
+
label: humanizeIdentifier4(id),
|
|
8639
|
+
summary: null,
|
|
8640
|
+
targetObjectId: null,
|
|
8641
|
+
participantObjectIds: [],
|
|
8642
|
+
timing: null,
|
|
8643
|
+
visibility: null,
|
|
8644
|
+
tags: [],
|
|
8645
|
+
color: null,
|
|
8646
|
+
hidden: false,
|
|
8647
|
+
positions: []
|
|
8648
|
+
};
|
|
8649
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
8650
|
+
nextDocument.events.push(created);
|
|
8651
|
+
nextDocument.events.sort(compareEvents);
|
|
8652
|
+
replaceAtlasDocument(nextDocument, true, { kind: "event", id });
|
|
8653
|
+
return id;
|
|
8654
|
+
},
|
|
7909
8655
|
addViewpoint() {
|
|
7910
8656
|
const id = createUniqueId("viewpoint", atlasDocument.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []);
|
|
7911
8657
|
const created = {
|
|
@@ -7914,6 +8660,7 @@
|
|
|
7914
8660
|
summary: "",
|
|
7915
8661
|
focusObjectId: null,
|
|
7916
8662
|
selectedObjectId: null,
|
|
8663
|
+
events: [],
|
|
7917
8664
|
projection: atlasDocument.system?.defaults.view ?? "topdown",
|
|
7918
8665
|
preset: atlasDocument.system?.defaults.preset ?? null,
|
|
7919
8666
|
zoom: null,
|
|
@@ -7974,6 +8721,7 @@
|
|
|
7974
8721
|
toolbar.removeEventListener("click", handleToolbarClick);
|
|
7975
8722
|
outline.removeEventListener("click", handleOutlineClick);
|
|
7976
8723
|
overlay.removeEventListener("pointerdown", handleOverlayPointerDown);
|
|
8724
|
+
inspector?.removeEventListener("click", handleInspectorClick);
|
|
7977
8725
|
inspector?.removeEventListener("input", handleInspectorInput);
|
|
7978
8726
|
inspector?.removeEventListener("change", handleInspectorChange);
|
|
7979
8727
|
sourcePane?.removeEventListener("input", handleSourceInput);
|
|
@@ -8019,7 +8767,7 @@
|
|
|
8019
8767
|
}
|
|
8020
8768
|
function getCurrentSourceForExport() {
|
|
8021
8769
|
if (dragState?.changed) {
|
|
8022
|
-
return
|
|
8770
|
+
return formatAtlasSource(atlasDocument);
|
|
8023
8771
|
}
|
|
8024
8772
|
return canonicalSource;
|
|
8025
8773
|
}
|
|
@@ -8058,7 +8806,7 @@
|
|
|
8058
8806
|
}
|
|
8059
8807
|
clearSourceInputTimer();
|
|
8060
8808
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
8061
|
-
canonicalSource =
|
|
8809
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8062
8810
|
if (!preserveSourceText) {
|
|
8063
8811
|
sourceText = canonicalSource;
|
|
8064
8812
|
}
|
|
@@ -8099,10 +8847,10 @@
|
|
|
8099
8847
|
if (commitHistory) {
|
|
8100
8848
|
history.push(createHistoryEntry());
|
|
8101
8849
|
future.length = 0;
|
|
8102
|
-
sourceText =
|
|
8850
|
+
sourceText = formatAtlasSource(nextDocument);
|
|
8103
8851
|
}
|
|
8104
8852
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
8105
|
-
canonicalSource =
|
|
8853
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8106
8854
|
diagnostics = mergeDiagnostics(loadedDiagnostics, collectDocumentDiagnostics(atlasDocument));
|
|
8107
8855
|
selection = normalizeSelection(selection);
|
|
8108
8856
|
syncViewer({
|
|
@@ -8121,11 +8869,13 @@
|
|
|
8121
8869
|
const previousState = viewer.getState();
|
|
8122
8870
|
const currentRenderOptions = viewer.getRenderOptions();
|
|
8123
8871
|
const nextPreset = atlasDocument.system?.defaults.preset ?? "atlas-card";
|
|
8872
|
+
const nextActiveEventId = selection ? selectionEventId(selection) : null;
|
|
8124
8873
|
ignoreViewerSelection = true;
|
|
8125
|
-
if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document") {
|
|
8874
|
+
if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document" || (currentRenderOptions.activeEventId ?? null) !== nextActiveEventId) {
|
|
8126
8875
|
viewer.setRenderOptions({
|
|
8127
8876
|
preset: nextPreset,
|
|
8128
|
-
projection: "document"
|
|
8877
|
+
projection: "document",
|
|
8878
|
+
activeEventId: nextActiveEventId
|
|
8129
8879
|
});
|
|
8130
8880
|
}
|
|
8131
8881
|
viewer.setDocument(materializeAtlasDocument(atlasDocument));
|
|
@@ -8134,10 +8884,12 @@
|
|
|
8134
8884
|
} else if (options2.preserveCamera !== false) {
|
|
8135
8885
|
viewer.setState({
|
|
8136
8886
|
...previousState,
|
|
8137
|
-
selectedObjectId: selection?.kind === "object" ? selection.id ?? null : null
|
|
8887
|
+
selectedObjectId: selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null
|
|
8138
8888
|
});
|
|
8139
8889
|
} else if (selection?.kind === "object" && selection.id) {
|
|
8140
8890
|
viewer.focusObject(selection.id);
|
|
8891
|
+
} else if (selection?.kind === "event-pose" && selection.key) {
|
|
8892
|
+
viewer.focusObject(selection.key);
|
|
8141
8893
|
}
|
|
8142
8894
|
ignoreViewerSelection = false;
|
|
8143
8895
|
}
|
|
@@ -8156,8 +8908,11 @@
|
|
|
8156
8908
|
selection = normalizeSelection(nextSelection);
|
|
8157
8909
|
if (syncViewerSelection) {
|
|
8158
8910
|
ignoreViewerSelection = true;
|
|
8911
|
+
viewer.setRenderOptions({ activeEventId: selection ? selectionEventId(selection) : null });
|
|
8159
8912
|
if (selection?.kind === "object" && selection.id) {
|
|
8160
8913
|
viewer.focusObject(selection.id);
|
|
8914
|
+
} else if (selection?.kind === "event-pose" && selection.key) {
|
|
8915
|
+
viewer.focusObject(selection.key);
|
|
8161
8916
|
} else if (selection?.kind === "viewpoint" && selection.id) {
|
|
8162
8917
|
viewer.goToViewpoint(selection.id);
|
|
8163
8918
|
}
|
|
@@ -8207,6 +8962,7 @@
|
|
|
8207
8962
|
${OBJECT_TYPES.map((type) => `<option value="${escapeHtml3(type)}"${type === objectType ? " selected" : ""}>${escapeHtml3(humanizeIdentifier4(type))}</option>`).join("")}
|
|
8208
8963
|
</select>
|
|
8209
8964
|
<button type="button" data-editor-action="add-object">Add object</button>
|
|
8965
|
+
<button type="button" data-editor-action="add-event">Add event</button>
|
|
8210
8966
|
<button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
|
|
8211
8967
|
<button type="button" data-editor-action="add-annotation">Add annotation</button>
|
|
8212
8968
|
<button type="button" data-editor-action="add-metadata">Add metadata</button>
|
|
@@ -8241,6 +8997,10 @@
|
|
|
8241
8997
|
<h3>Annotations</h3>
|
|
8242
8998
|
${(atlasDocument.system?.annotations.length ?? 0) > 0 ? atlasDocument.system?.annotations.map((annotation) => renderOutlineButton({ kind: "annotation", id: annotation.id }, annotation.label, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No annotations yet.</p>`}
|
|
8243
8999
|
</div>
|
|
9000
|
+
<div class="wo-editor-outline-section">
|
|
9001
|
+
<h3>Events</h3>
|
|
9002
|
+
${atlasDocument.events.length > 0 ? atlasDocument.events.map((eventEntry) => renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No events yet.</p>`}
|
|
9003
|
+
</div>
|
|
8244
9004
|
<div class="wo-editor-outline-section">
|
|
8245
9005
|
<h3>Objects</h3>
|
|
8246
9006
|
${atlasDocument.objects.length > 0 ? atlasDocument.objects.map((object) => renderOutlineButton({ kind: "object", id: object.id }, `${object.id} - ${object.type}`, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No objects yet.</p>`}
|
|
@@ -8278,6 +9038,7 @@
|
|
|
8278
9038
|
selection: selection ? { path: { ...selection } } : null,
|
|
8279
9039
|
system: atlasDocument.system,
|
|
8280
9040
|
viewpoints: atlasDocument.system?.viewpoints ?? [],
|
|
9041
|
+
events: atlasDocument.events,
|
|
8281
9042
|
objects: atlasDocument.objects
|
|
8282
9043
|
};
|
|
8283
9044
|
if (!selection) {
|
|
@@ -8306,6 +9067,16 @@
|
|
|
8306
9067
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
8307
9068
|
decorateInspectorDiagnostics(selection, diagnostics);
|
|
8308
9069
|
return;
|
|
9070
|
+
case "event":
|
|
9071
|
+
inspector.innerHTML = diagnosticSummary + renderEventInspector(formState, selection.id ?? "");
|
|
9072
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
9073
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
9074
|
+
return;
|
|
9075
|
+
case "event-pose":
|
|
9076
|
+
inspector.innerHTML = diagnosticSummary + renderEventPoseInspector(formState, selection.id ?? "", selection.key ?? "");
|
|
9077
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
9078
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
9079
|
+
return;
|
|
8309
9080
|
case "annotation":
|
|
8310
9081
|
inspector.innerHTML = diagnosticSummary + renderAnnotationInspector(formState, selection.id ?? "");
|
|
8311
9082
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
@@ -8338,10 +9109,11 @@
|
|
|
8338
9109
|
return;
|
|
8339
9110
|
}
|
|
8340
9111
|
overlay.innerHTML = "";
|
|
8341
|
-
|
|
9112
|
+
const selectedObjectId = selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null;
|
|
9113
|
+
if (!selectedObjectId) {
|
|
8342
9114
|
return;
|
|
8343
9115
|
}
|
|
8344
|
-
const details = viewer.getObjectDetails(
|
|
9116
|
+
const details = viewer.getObjectDetails(selectedObjectId);
|
|
8345
9117
|
if (!details) {
|
|
8346
9118
|
return;
|
|
8347
9119
|
}
|
|
@@ -8453,6 +9225,9 @@
|
|
|
8453
9225
|
case "add-viewpoint":
|
|
8454
9226
|
api.addViewpoint();
|
|
8455
9227
|
return;
|
|
9228
|
+
case "add-event":
|
|
9229
|
+
api.addEvent();
|
|
9230
|
+
return;
|
|
8456
9231
|
case "add-annotation":
|
|
8457
9232
|
api.addAnnotation();
|
|
8458
9233
|
return;
|
|
@@ -8487,6 +9262,32 @@
|
|
|
8487
9262
|
key: button.dataset.pathKey || void 0
|
|
8488
9263
|
}, true, true);
|
|
8489
9264
|
}
|
|
9265
|
+
function handleInspectorClick(event) {
|
|
9266
|
+
const pathButton = event.target?.closest("[data-path-kind]");
|
|
9267
|
+
if (pathButton) {
|
|
9268
|
+
setSelection({
|
|
9269
|
+
kind: pathButton.dataset.pathKind,
|
|
9270
|
+
id: pathButton.dataset.pathId || void 0,
|
|
9271
|
+
key: pathButton.dataset.pathKey || void 0
|
|
9272
|
+
}, true, true);
|
|
9273
|
+
return;
|
|
9274
|
+
}
|
|
9275
|
+
const actionButton = event.target?.closest("[data-editor-action]");
|
|
9276
|
+
if (!actionButton) {
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
if (actionButton.dataset.editorAction === "add-event-pose") {
|
|
9280
|
+
const eventId = actionButton.dataset.editorEventId || (selection?.kind === "event" || selection?.kind === "event-pose" ? selection.id ?? "" : "");
|
|
9281
|
+
if (!eventId) {
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
const nextDocument = addEventPose(atlasDocument, eventId);
|
|
9285
|
+
const createdEvent = nextDocument.events.find((entry) => entry.id === eventId);
|
|
9286
|
+
const createdPose = createdEvent?.positions.at(-1) ?? createdEvent?.positions[0];
|
|
9287
|
+
replaceAtlasDocument(nextDocument, true, createdPose ? { kind: "event-pose", id: eventId, key: createdPose.objectId } : { kind: "event", id: eventId });
|
|
9288
|
+
return;
|
|
9289
|
+
}
|
|
9290
|
+
}
|
|
8490
9291
|
function handleInspectorInput() {
|
|
8491
9292
|
applyInspectorState(false);
|
|
8492
9293
|
}
|
|
@@ -8510,6 +9311,12 @@
|
|
|
8510
9311
|
case "viewpoint":
|
|
8511
9312
|
replaceAtlasDocument(buildViewpointDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
8512
9313
|
return;
|
|
9314
|
+
case "event":
|
|
9315
|
+
replaceAtlasDocument(buildEventDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
9316
|
+
return;
|
|
9317
|
+
case "event-pose":
|
|
9318
|
+
replaceAtlasDocument(buildEventPoseDocumentFromInspector(selection.id ?? "", selection.key ?? ""), commitHistory, selection, false);
|
|
9319
|
+
return;
|
|
8513
9320
|
case "annotation":
|
|
8514
9321
|
replaceAtlasDocument(buildAnnotationDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
8515
9322
|
return;
|
|
@@ -8578,6 +9385,7 @@
|
|
|
8578
9385
|
kind,
|
|
8579
9386
|
objectId,
|
|
8580
9387
|
pointerId: event.pointerId,
|
|
9388
|
+
path: selection ? { ...selection } : { kind: "object", id: objectId },
|
|
8581
9389
|
startedFrom: createHistoryEntry(),
|
|
8582
9390
|
changed: false,
|
|
8583
9391
|
orbitRadiusContext: kind === "orbit-radius" && details ? createOrbitRadiusDragContext(atlasDocument, viewer.getScene(), details) : null
|
|
@@ -8586,7 +9394,7 @@
|
|
|
8586
9394
|
event.preventDefault();
|
|
8587
9395
|
}
|
|
8588
9396
|
function handleWindowPointerMove(event) {
|
|
8589
|
-
if (!dragState || dragState.pointerId !== event.pointerId || selection
|
|
9397
|
+
if (!dragState || dragState.pointerId !== event.pointerId || !selection || selectionKey(selection) !== selectionKey(dragState.path)) {
|
|
8590
9398
|
return;
|
|
8591
9399
|
}
|
|
8592
9400
|
const details = viewer.getObjectDetails(dragState.objectId);
|
|
@@ -8598,27 +9406,27 @@
|
|
|
8598
9406
|
switch (dragState.kind) {
|
|
8599
9407
|
case "orbit-phase":
|
|
8600
9408
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
8601
|
-
nextDocument = updateOrbitPhase(atlasDocument, dragState.objectId, details, pointer);
|
|
9409
|
+
nextDocument = updateOrbitPhase(atlasDocument, dragState.path, dragState.objectId, details, pointer);
|
|
8602
9410
|
}
|
|
8603
9411
|
break;
|
|
8604
9412
|
case "orbit-radius":
|
|
8605
9413
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
8606
|
-
nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
9414
|
+
nextDocument = updateOrbitRadius(atlasDocument, dragState.path, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
8607
9415
|
}
|
|
8608
9416
|
break;
|
|
8609
9417
|
case "at-reference":
|
|
8610
9418
|
if (details.object.placement?.mode === "at") {
|
|
8611
|
-
nextDocument = updateAtReference(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
9419
|
+
nextDocument = updateAtReference(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
8612
9420
|
}
|
|
8613
9421
|
break;
|
|
8614
9422
|
case "surface-target":
|
|
8615
9423
|
if (details.object.placement?.mode === "surface") {
|
|
8616
|
-
nextDocument = updateSurfaceTarget(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
9424
|
+
nextDocument = updateSurfaceTarget(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
8617
9425
|
}
|
|
8618
9426
|
break;
|
|
8619
9427
|
case "free-distance":
|
|
8620
9428
|
if (details.object.placement?.mode === "free") {
|
|
8621
|
-
nextDocument = updateFreeDistance(atlasDocument, dragState.objectId, viewer.getScene(), details, pointer);
|
|
9429
|
+
nextDocument = updateFreeDistance(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), details, pointer);
|
|
8622
9430
|
}
|
|
8623
9431
|
break;
|
|
8624
9432
|
}
|
|
@@ -8650,7 +9458,7 @@
|
|
|
8650
9458
|
}
|
|
8651
9459
|
history.push(dragState.startedFrom);
|
|
8652
9460
|
future.length = 0;
|
|
8653
|
-
canonicalSource =
|
|
9461
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8654
9462
|
sourceText = canonicalSource;
|
|
8655
9463
|
dragState = null;
|
|
8656
9464
|
renderAll();
|
|
@@ -8753,10 +9561,12 @@
|
|
|
8753
9561
|
guides: readCheckbox(form, "layer-guides"),
|
|
8754
9562
|
"orbits-back": readCheckbox(form, "layer-orbits-back"),
|
|
8755
9563
|
"orbits-front": readCheckbox(form, "layer-orbits-front"),
|
|
9564
|
+
events: readCheckbox(form, "layer-events"),
|
|
8756
9565
|
objects: readCheckbox(form, "layer-objects"),
|
|
8757
9566
|
labels: readCheckbox(form, "layer-labels"),
|
|
8758
9567
|
metadata: readCheckbox(form, "layer-metadata")
|
|
8759
9568
|
},
|
|
9569
|
+
events: splitTokens(readOptionalTextInput(form, "viewpoint-events")),
|
|
8760
9570
|
filter: {
|
|
8761
9571
|
query: readOptionalTextInput(form, "filter-query"),
|
|
8762
9572
|
objectTypes: parseObjectTypes(readOptionalTextInput(form, "filter-object-types")),
|
|
@@ -8770,6 +9580,66 @@
|
|
|
8770
9580
|
}
|
|
8771
9581
|
return nextDocument;
|
|
8772
9582
|
}
|
|
9583
|
+
function buildEventDocumentFromInspector(currentId) {
|
|
9584
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
9585
|
+
const form = inspector?.querySelector("form[data-editor-form='event']");
|
|
9586
|
+
const current = nextDocument.events.find((entry) => entry.id === currentId);
|
|
9587
|
+
if (!form || !current) {
|
|
9588
|
+
return nextDocument;
|
|
9589
|
+
}
|
|
9590
|
+
const nextId = readTextInput(form, "event-id") || current.id;
|
|
9591
|
+
const replacement = {
|
|
9592
|
+
...current,
|
|
9593
|
+
id: nextId,
|
|
9594
|
+
kind: readTextInput(form, "event-kind"),
|
|
9595
|
+
label: readTextInput(form, "event-label") || current.label,
|
|
9596
|
+
summary: readOptionalTextInput(form, "event-summary"),
|
|
9597
|
+
targetObjectId: readOptionalTextInput(form, "event-target"),
|
|
9598
|
+
participantObjectIds: splitTokens(readOptionalTextInput(form, "event-participants")),
|
|
9599
|
+
timing: readOptionalTextInput(form, "event-timing"),
|
|
9600
|
+
visibility: readOptionalTextInput(form, "event-visibility"),
|
|
9601
|
+
tags: splitTokens(readOptionalTextInput(form, "event-tags")),
|
|
9602
|
+
color: readOptionalTextInput(form, "event-color"),
|
|
9603
|
+
hidden: readCheckbox(form, "event-hidden")
|
|
9604
|
+
};
|
|
9605
|
+
nextDocument.events = nextDocument.events.filter((entry) => entry.id !== current.id).concat(replacement).sort(compareEvents);
|
|
9606
|
+
syncEventViewpointReferences(nextDocument, current.id, replacement.id, splitTokens(readOptionalTextInput(form, "event-viewpoints")));
|
|
9607
|
+
if (current.id !== replacement.id) {
|
|
9608
|
+
selection = { kind: "event", id: replacement.id };
|
|
9609
|
+
}
|
|
9610
|
+
return nextDocument;
|
|
9611
|
+
}
|
|
9612
|
+
function buildEventPoseDocumentFromInspector(eventId, objectId) {
|
|
9613
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
9614
|
+
const form = inspector?.querySelector("form[data-editor-form='event-pose']");
|
|
9615
|
+
const eventEntry = nextDocument.events.find((entry) => entry.id === eventId);
|
|
9616
|
+
const currentPose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
9617
|
+
if (!form || !eventEntry || !currentPose) {
|
|
9618
|
+
return nextDocument;
|
|
9619
|
+
}
|
|
9620
|
+
const nextObjectId = readTextInput(form, "pose-object-id") || currentPose.objectId;
|
|
9621
|
+
const replacement = {
|
|
9622
|
+
objectId: nextObjectId,
|
|
9623
|
+
placement: buildPlacementFromPoseForm(form, currentPose)
|
|
9624
|
+
};
|
|
9625
|
+
const inner = parseOptionalUnit(readOptionalTextInput(form, "prop-inner"));
|
|
9626
|
+
const outer = parseOptionalUnit(readOptionalTextInput(form, "prop-outer"));
|
|
9627
|
+
if (inner) {
|
|
9628
|
+
replacement.inner = inner;
|
|
9629
|
+
}
|
|
9630
|
+
if (outer) {
|
|
9631
|
+
replacement.outer = outer;
|
|
9632
|
+
}
|
|
9633
|
+
eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== currentPose.objectId).concat(replacement).sort(compareEventPoses);
|
|
9634
|
+
if (eventEntry.targetObjectId !== replacement.objectId && !eventEntry.participantObjectIds.includes(replacement.objectId)) {
|
|
9635
|
+
eventEntry.participantObjectIds.push(replacement.objectId);
|
|
9636
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
9637
|
+
}
|
|
9638
|
+
if (currentPose.objectId !== replacement.objectId) {
|
|
9639
|
+
selection = { kind: "event-pose", id: eventId, key: replacement.objectId };
|
|
9640
|
+
}
|
|
9641
|
+
return nextDocument;
|
|
9642
|
+
}
|
|
8773
9643
|
function buildAnnotationDocumentFromInspector(currentId) {
|
|
8774
9644
|
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
8775
9645
|
const form = inspector?.querySelector("form[data-editor-form='annotation']");
|
|
@@ -8962,7 +9832,7 @@
|
|
|
8962
9832
|
const atlasDocument2 = cloneAtlasDocument(options.atlasDocument);
|
|
8963
9833
|
return {
|
|
8964
9834
|
atlasDocument: atlasDocument2,
|
|
8965
|
-
source:
|
|
9835
|
+
source: formatAtlasSource(atlasDocument2),
|
|
8966
9836
|
diagnostics: collectDocumentDiagnostics(atlasDocument2)
|
|
8967
9837
|
};
|
|
8968
9838
|
}
|
|
@@ -8972,7 +9842,7 @@
|
|
|
8972
9842
|
const atlasDocument2 = loaded.value.atlasDocument ?? upgradeDocumentToV2(loaded.value.document);
|
|
8973
9843
|
return {
|
|
8974
9844
|
atlasDocument: atlasDocument2,
|
|
8975
|
-
source:
|
|
9845
|
+
source: formatAtlasSource(atlasDocument2),
|
|
8976
9846
|
diagnostics: mergeDiagnostics(resolveAtlasDiagnostics(atlasDocument2, loaded.diagnostics), collectDocumentDiagnostics(atlasDocument2))
|
|
8977
9847
|
};
|
|
8978
9848
|
}
|
|
@@ -8980,10 +9850,13 @@
|
|
|
8980
9850
|
const atlasDocument = createEmptyAtlasDocument("WorldOrbit");
|
|
8981
9851
|
return {
|
|
8982
9852
|
atlasDocument,
|
|
8983
|
-
source:
|
|
9853
|
+
source: formatAtlasSource(atlasDocument),
|
|
8984
9854
|
diagnostics: collectDocumentDiagnostics(atlasDocument)
|
|
8985
9855
|
};
|
|
8986
9856
|
}
|
|
9857
|
+
function formatAtlasSource(document2) {
|
|
9858
|
+
return formatDocument(document2, { schema: document2.version });
|
|
9859
|
+
}
|
|
8987
9860
|
function buildEditorMarkup() {
|
|
8988
9861
|
const previewOpen = shouldPreviewSectionBeOpenByDefault();
|
|
8989
9862
|
return `<section class="wo-editor-shell">
|
|
@@ -9098,6 +9971,12 @@
|
|
|
9098
9971
|
const badge = bucket && (bucket.errors > 0 || bucket.warnings > 0) ? `<span class="wo-editor-outline-badge${bucket.errors > 0 ? " is-error" : " is-warning"}">${bucket.errors > 0 ? bucket.errors : bucket.warnings}</span>` : "";
|
|
9099
9972
|
return `<button type="button" class="wo-editor-outline-item${key === activeKey ? " is-active" : ""}" data-path-kind="${escapeHtml3(path.kind)}"${path.id ? ` data-path-id="${escapeHtml3(path.id)}"` : ""}${path.key ? ` data-path-key="${escapeHtml3(path.key)}"` : ""}><span>${escapeHtml3(label)}</span>${badge}</button>`;
|
|
9100
9973
|
}
|
|
9974
|
+
function renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets) {
|
|
9975
|
+
return `<div class="wo-editor-outline-group">
|
|
9976
|
+
${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
|
|
9977
|
+
${eventEntry.positions.length > 0 ? `<div class="wo-editor-outline-children">${[...eventEntry.positions].sort(compareEventPoses).map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, activeKey, diagnosticBuckets)).join("")}</div>` : ""}
|
|
9978
|
+
</div>`;
|
|
9979
|
+
}
|
|
9101
9980
|
function renderSystemInspector(formState) {
|
|
9102
9981
|
return `<form class="wo-editor-form" data-editor-form="system">
|
|
9103
9982
|
<h2>System</h2>
|
|
@@ -9164,6 +10043,7 @@
|
|
|
9164
10043
|
${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
|
|
9165
10044
|
${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
|
|
9166
10045
|
${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
|
|
10046
|
+
${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
|
|
9167
10047
|
${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
|
|
9168
10048
|
${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
|
|
9169
10049
|
${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
|
|
@@ -9171,7 +10051,70 @@
|
|
|
9171
10051
|
${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
|
|
9172
10052
|
${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
|
|
9173
10053
|
${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
|
|
9174
|
-
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
10054
|
+
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
10055
|
+
${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
|
|
10056
|
+
</form>`;
|
|
10057
|
+
}
|
|
10058
|
+
function renderEventInspector(formState, id) {
|
|
10059
|
+
const eventEntry = formState.events.find((entry) => entry.id === id);
|
|
10060
|
+
if (!eventEntry) {
|
|
10061
|
+
return `<p class="wo-editor-empty">Event not found.</p>`;
|
|
10062
|
+
}
|
|
10063
|
+
const linkedViewpoints = formState.viewpoints.filter((viewpoint) => viewpoint.events.includes(eventEntry.id)).map((viewpoint) => viewpoint.id).join(" ");
|
|
10064
|
+
return `<form class="wo-editor-form" data-editor-form="event">
|
|
10065
|
+
<h2>Event</h2>
|
|
10066
|
+
${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
|
|
10067
|
+
${renderTextField("Kind", "event-kind", eventEntry.kind)}
|
|
10068
|
+
${renderTextField("Label", "event-label", eventEntry.label)}
|
|
10069
|
+
${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
|
|
10070
|
+
${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
|
|
10071
|
+
${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
|
|
10072
|
+
${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
|
|
10073
|
+
${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
|
|
10074
|
+
${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
|
|
10075
|
+
${renderTextField("Color", "event-color", eventEntry.color ?? "")}
|
|
10076
|
+
${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
|
|
10077
|
+
${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
|
|
10078
|
+
${renderInspectorSection("event", "positions", "Positions", `${eventEntry.positions.length > 0 ? `<div class="wo-editor-inline-list">${eventEntry.positions.map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, null, /* @__PURE__ */ new Map())).join("")}</div>` : `<p class="wo-editor-empty">No event poses yet.</p>`}
|
|
10079
|
+
<div class="wo-editor-inline-actions">
|
|
10080
|
+
<button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml3(eventEntry.id)}">Add pose</button>
|
|
10081
|
+
</div>`)}
|
|
10082
|
+
</form>`;
|
|
10083
|
+
}
|
|
10084
|
+
function renderEventPoseInspector(formState, eventId, objectId) {
|
|
10085
|
+
const eventEntry = formState.events.find((entry) => entry.id === eventId);
|
|
10086
|
+
const pose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
10087
|
+
if (!eventEntry || !pose) {
|
|
10088
|
+
return `<p class="wo-editor-empty">Event pose not found.</p>`;
|
|
10089
|
+
}
|
|
10090
|
+
const placementMode = pose.placement?.mode ?? "";
|
|
10091
|
+
const placementTarget = pose.placement?.mode === "orbit" || pose.placement?.mode === "surface" || pose.placement?.mode === "at" ? pose.placement.target : "";
|
|
10092
|
+
const freeValue = pose.placement?.mode === "free" ? pose.placement.distance ? formatUnitValue3(pose.placement.distance) : pose.placement.descriptor ?? "" : "";
|
|
10093
|
+
return `<form class="wo-editor-form" data-editor-form="event-pose">
|
|
10094
|
+
<h2>Event Pose</h2>
|
|
10095
|
+
<p class="wo-editor-inline-note">Event <strong>${escapeHtml3(eventEntry.label || eventEntry.id)}</strong></p>
|
|
10096
|
+
${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
|
|
10097
|
+
<div class="wo-editor-inline-actions">
|
|
10098
|
+
<button type="button" data-path-kind="event" data-path-id="${escapeHtml3(eventEntry.id)}">Select event</button>
|
|
10099
|
+
</div>`, true)}
|
|
10100
|
+
${renderInspectorSection("event-pose", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
|
|
10101
|
+
["", "None"],
|
|
10102
|
+
["orbit", "Orbit"],
|
|
10103
|
+
["at", "At"],
|
|
10104
|
+
["surface", "Surface"],
|
|
10105
|
+
["free", "Free"]
|
|
10106
|
+
], placementMode)}
|
|
10107
|
+
${renderTextField("Placement target", "placement-target", placementTarget)}
|
|
10108
|
+
${renderTextField("Free value", "placement-free", freeValue)}
|
|
10109
|
+
${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue3(pose.placement.distance) : "")}
|
|
10110
|
+
${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue3(pose.placement.semiMajor) : "")}
|
|
10111
|
+
${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== void 0 ? String(pose.placement.eccentricity) : "")}
|
|
10112
|
+
${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue3(pose.placement.period) : "")}
|
|
10113
|
+
${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue3(pose.placement.angle) : "")}
|
|
10114
|
+
${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue3(pose.placement.inclination) : "")}
|
|
10115
|
+
${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue3(pose.placement.phase) : "")}
|
|
10116
|
+
${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue3(pose.inner) : "")}
|
|
10117
|
+
${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue3(pose.outer) : "")}`, true)}
|
|
9175
10118
|
</form>`;
|
|
9176
10119
|
}
|
|
9177
10120
|
function renderAnnotationInspector(formState, id) {
|
|
@@ -9276,13 +10219,19 @@
|
|
|
9276
10219
|
return form.elements.namedItem(name)?.checked ?? false;
|
|
9277
10220
|
}
|
|
9278
10221
|
function buildPlacementFromForm(form, current) {
|
|
10222
|
+
return buildPlacementFromValues(form, current.placement, current.id);
|
|
10223
|
+
}
|
|
10224
|
+
function buildPlacementFromPoseForm(form, current) {
|
|
10225
|
+
return buildPlacementFromValues(form, current.placement, current.objectId);
|
|
10226
|
+
}
|
|
10227
|
+
function buildPlacementFromValues(form, currentPlacement, fallbackTarget) {
|
|
9279
10228
|
const mode = readTextInput(form, "placement-mode");
|
|
9280
10229
|
const target = readOptionalTextInput(form, "placement-target");
|
|
9281
10230
|
switch (mode) {
|
|
9282
10231
|
case "orbit":
|
|
9283
10232
|
return {
|
|
9284
10233
|
mode,
|
|
9285
|
-
target: target ?? (
|
|
10234
|
+
target: target ?? (currentPlacement?.mode === "orbit" ? currentPlacement.target : fallbackTarget),
|
|
9286
10235
|
distance: parseOptionalUnit(readOptionalTextInput(form, "placement-distance")),
|
|
9287
10236
|
semiMajor: parseOptionalUnit(readOptionalTextInput(form, "placement-semiMajor")),
|
|
9288
10237
|
eccentricity: parseNullableNumber(readOptionalTextInput(form, "placement-eccentricity")) ?? void 0,
|
|
@@ -9294,13 +10243,13 @@
|
|
|
9294
10243
|
case "at":
|
|
9295
10244
|
return {
|
|
9296
10245
|
mode,
|
|
9297
|
-
target: target ??
|
|
9298
|
-
reference: parseAtReferenceString(target ??
|
|
10246
|
+
target: target ?? fallbackTarget,
|
|
10247
|
+
reference: parseAtReferenceString(target ?? fallbackTarget)
|
|
9299
10248
|
};
|
|
9300
10249
|
case "surface":
|
|
9301
10250
|
return {
|
|
9302
10251
|
mode,
|
|
9303
|
-
target: target ??
|
|
10252
|
+
target: target ?? fallbackTarget
|
|
9304
10253
|
};
|
|
9305
10254
|
case "free": {
|
|
9306
10255
|
const freeValue = readOptionalTextInput(form, "placement-free");
|
|
@@ -9431,9 +10380,48 @@
|
|
|
9431
10380
|
annotation.sourceObjectId = toId;
|
|
9432
10381
|
}
|
|
9433
10382
|
}
|
|
10383
|
+
for (const eventEntry of document2.events) {
|
|
10384
|
+
if (eventEntry.targetObjectId === fromId) {
|
|
10385
|
+
eventEntry.targetObjectId = toId;
|
|
10386
|
+
}
|
|
10387
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.map((entry) => entry === fromId ? toId : entry);
|
|
10388
|
+
for (const pose of eventEntry.positions) {
|
|
10389
|
+
if (pose.objectId === fromId) {
|
|
10390
|
+
pose.objectId = toId;
|
|
10391
|
+
}
|
|
10392
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === fromId) {
|
|
10393
|
+
pose.placement.target = toId;
|
|
10394
|
+
}
|
|
10395
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === fromId) {
|
|
10396
|
+
pose.placement.target = toId;
|
|
10397
|
+
}
|
|
10398
|
+
if (pose.placement?.mode === "at") {
|
|
10399
|
+
const reference = pose.placement.reference;
|
|
10400
|
+
if (reference.kind === "anchor" && reference.objectId === fromId) {
|
|
10401
|
+
reference.objectId = toId;
|
|
10402
|
+
}
|
|
10403
|
+
if (reference.kind === "lagrange") {
|
|
10404
|
+
if (reference.primary === fromId) {
|
|
10405
|
+
reference.primary = toId;
|
|
10406
|
+
}
|
|
10407
|
+
if (reference.secondary === fromId) {
|
|
10408
|
+
reference.secondary = toId;
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
pose.placement.target = formatAtReference2(reference);
|
|
10412
|
+
}
|
|
10413
|
+
}
|
|
10414
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
10415
|
+
}
|
|
9434
10416
|
}
|
|
9435
10417
|
function removeSelectedNode(document2, selection) {
|
|
9436
10418
|
const next = removeAtlasDocumentNode(document2, selection);
|
|
10419
|
+
if (selection.kind === "event" && selection.id) {
|
|
10420
|
+
for (const viewpoint of next.system?.viewpoints ?? []) {
|
|
10421
|
+
viewpoint.events = viewpoint.events.filter((eventId) => eventId !== selection.id);
|
|
10422
|
+
}
|
|
10423
|
+
return next;
|
|
10424
|
+
}
|
|
9437
10425
|
if (selection.kind !== "object" || !selection.id) {
|
|
9438
10426
|
return next;
|
|
9439
10427
|
}
|
|
@@ -9468,9 +10456,45 @@
|
|
|
9468
10456
|
annotation.sourceObjectId = null;
|
|
9469
10457
|
}
|
|
9470
10458
|
}
|
|
10459
|
+
for (const eventEntry of next.events) {
|
|
10460
|
+
if (eventEntry.targetObjectId === selection.id) {
|
|
10461
|
+
eventEntry.targetObjectId = null;
|
|
10462
|
+
}
|
|
10463
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.filter((entry) => entry !== selection.id);
|
|
10464
|
+
eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== selection.id);
|
|
10465
|
+
for (const pose of eventEntry.positions) {
|
|
10466
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === selection.id) {
|
|
10467
|
+
pose.placement = null;
|
|
10468
|
+
}
|
|
10469
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === selection.id) {
|
|
10470
|
+
pose.placement = null;
|
|
10471
|
+
}
|
|
10472
|
+
if (pose.placement?.mode === "at") {
|
|
10473
|
+
const reference = pose.placement.reference;
|
|
10474
|
+
const touchesSelection = reference.kind === "anchor" && reference.objectId === selection.id || reference.kind === "lagrange" && (reference.primary === selection.id || reference.secondary === selection.id);
|
|
10475
|
+
if (touchesSelection) {
|
|
10476
|
+
pose.placement = null;
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
}
|
|
9471
10481
|
return next;
|
|
9472
10482
|
}
|
|
9473
|
-
function
|
|
10483
|
+
function findEditablePlacementOwner(document2, path, objectId) {
|
|
10484
|
+
if (path.kind === "event-pose" && path.id && path.key) {
|
|
10485
|
+
const pose = findEventPose2(document2, path.id, path.key);
|
|
10486
|
+
if (pose?.placement) {
|
|
10487
|
+
return { placement: pose.placement };
|
|
10488
|
+
}
|
|
10489
|
+
return null;
|
|
10490
|
+
}
|
|
10491
|
+
const object = findObject2(document2, objectId);
|
|
10492
|
+
if (object?.placement) {
|
|
10493
|
+
return { placement: object.placement };
|
|
10494
|
+
}
|
|
10495
|
+
return null;
|
|
10496
|
+
}
|
|
10497
|
+
function updateOrbitPhase(document2, path, objectId, details, pointer) {
|
|
9474
10498
|
const orbit = details.orbit;
|
|
9475
10499
|
if (!orbit || details.object.placement?.mode !== "orbit") {
|
|
9476
10500
|
return document2;
|
|
@@ -9481,17 +10505,17 @@
|
|
|
9481
10505
|
const radians = Math.atan2((unrotated.y - orbit.cy) / Math.max(ry, 1), (unrotated.x - orbit.cx) / Math.max(rx, 1));
|
|
9482
10506
|
const phaseDeg = normalizeDegrees(radians * 180 / Math.PI);
|
|
9483
10507
|
const next = cloneAtlasDocument(document2);
|
|
9484
|
-
const
|
|
9485
|
-
if (!
|
|
10508
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
10509
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
9486
10510
|
return document2;
|
|
9487
10511
|
}
|
|
9488
|
-
|
|
10512
|
+
placementOwner.placement.phase = {
|
|
9489
10513
|
value: roundNumber(phaseDeg, 2),
|
|
9490
10514
|
unit: "deg"
|
|
9491
10515
|
};
|
|
9492
10516
|
return next;
|
|
9493
10517
|
}
|
|
9494
|
-
function updateOrbitRadius(document2, objectId, details, pointer, dragContext) {
|
|
10518
|
+
function updateOrbitRadius(document2, path, objectId, details, pointer, dragContext) {
|
|
9495
10519
|
const orbit = details.orbit;
|
|
9496
10520
|
if (!orbit || details.object.placement?.mode !== "orbit" || !dragContext) {
|
|
9497
10521
|
return document2;
|
|
@@ -9501,47 +10525,47 @@
|
|
|
9501
10525
|
const nextBaseRadius = Math.max(nextDisplayedRadius - dragContext.radiusOffsetPx, dragContext.innerPx);
|
|
9502
10526
|
const nextMetric = orbitRadiusPxToMetric(nextBaseRadius, dragContext.innerPx, dragContext.stepPx);
|
|
9503
10527
|
const next = cloneAtlasDocument(document2);
|
|
9504
|
-
const
|
|
9505
|
-
if (!
|
|
10528
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
10529
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
9506
10530
|
return document2;
|
|
9507
10531
|
}
|
|
9508
|
-
const currentValue =
|
|
10532
|
+
const currentValue = placementOwner.placement.semiMajor ?? placementOwner.placement.distance ?? {
|
|
9509
10533
|
value: 1,
|
|
9510
10534
|
unit: "au"
|
|
9511
10535
|
};
|
|
9512
10536
|
const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
|
|
9513
|
-
if (
|
|
9514
|
-
|
|
10537
|
+
if (placementOwner.placement.semiMajor) {
|
|
10538
|
+
placementOwner.placement.semiMajor = scaled;
|
|
9515
10539
|
} else {
|
|
9516
|
-
|
|
10540
|
+
placementOwner.placement.distance = scaled;
|
|
9517
10541
|
}
|
|
9518
10542
|
return next;
|
|
9519
10543
|
}
|
|
9520
|
-
function updateAtReference(document2, objectId, scene, pointer) {
|
|
10544
|
+
function updateAtReference(document2, path, objectId, scene, pointer) {
|
|
9521
10545
|
const candidate = findNearestAtCandidate(scene, objectId, pointer);
|
|
9522
10546
|
if (!candidate) {
|
|
9523
10547
|
return document2;
|
|
9524
10548
|
}
|
|
9525
10549
|
const next = cloneAtlasDocument(document2);
|
|
9526
|
-
const
|
|
9527
|
-
if (!
|
|
10550
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
10551
|
+
if (!placementOwner || placementOwner.placement.mode !== "at") {
|
|
9528
10552
|
return document2;
|
|
9529
10553
|
}
|
|
9530
|
-
|
|
9531
|
-
|
|
10554
|
+
placementOwner.placement.reference = candidate.reference;
|
|
10555
|
+
placementOwner.placement.target = formatAtReference2(candidate.reference);
|
|
9532
10556
|
return next;
|
|
9533
10557
|
}
|
|
9534
|
-
function updateSurfaceTarget(document2, objectId, scene, pointer) {
|
|
10558
|
+
function updateSurfaceTarget(document2, path, objectId, scene, pointer) {
|
|
9535
10559
|
const target = findNearestSceneObject(scene, objectId, pointer, (entry) => SURFACE_TARGET_TYPES3.has(entry.object.type));
|
|
9536
10560
|
if (!target) {
|
|
9537
10561
|
return document2;
|
|
9538
10562
|
}
|
|
9539
10563
|
const next = cloneAtlasDocument(document2);
|
|
9540
|
-
const
|
|
9541
|
-
if (!
|
|
10564
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
10565
|
+
if (!placementOwner || placementOwner.placement.mode !== "surface") {
|
|
9542
10566
|
return document2;
|
|
9543
10567
|
}
|
|
9544
|
-
|
|
10568
|
+
placementOwner.placement.target = target.objectId;
|
|
9545
10569
|
return next;
|
|
9546
10570
|
}
|
|
9547
10571
|
function createOrbitRadiusDragContext(document2, scene, details) {
|
|
@@ -9549,7 +10573,7 @@
|
|
|
9549
10573
|
return null;
|
|
9550
10574
|
}
|
|
9551
10575
|
const targetId = details.object.placement.target;
|
|
9552
|
-
const siblingCount =
|
|
10576
|
+
const siblingCount = scene.objects.filter((entry) => entry.object.placement?.mode === "orbit" && entry.object.placement.target === targetId && !entry.hidden).length;
|
|
9553
10577
|
const spacingFactor = layoutPresetSpacingForScene(scene.layoutPreset);
|
|
9554
10578
|
const stepPx = (siblingCount > 2 ? 54 : 64) * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
|
|
9555
10579
|
const innerPx = details.parent.radius + 56 * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
|
|
@@ -9564,28 +10588,28 @@
|
|
|
9564
10588
|
preferredUnit: currentValue?.unit ?? null
|
|
9565
10589
|
};
|
|
9566
10590
|
}
|
|
9567
|
-
function updateFreeDistance(document2, objectId, scene, details, pointer) {
|
|
10591
|
+
function updateFreeDistance(document2, path, objectId, scene, details, pointer) {
|
|
9568
10592
|
if (details.object.placement?.mode !== "free") {
|
|
9569
10593
|
return document2;
|
|
9570
10594
|
}
|
|
9571
10595
|
const railX = scene.width - scene.padding - 140;
|
|
9572
10596
|
const offsetPx = Math.max(0, railX - pointer.x);
|
|
9573
10597
|
const next = cloneAtlasDocument(document2);
|
|
9574
|
-
const
|
|
9575
|
-
if (!
|
|
10598
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
10599
|
+
if (!placementOwner || placementOwner.placement.mode !== "free") {
|
|
9576
10600
|
return document2;
|
|
9577
10601
|
}
|
|
9578
|
-
const preferredUnit = normalizeFreeDistanceUnit(
|
|
10602
|
+
const preferredUnit = normalizeFreeDistanceUnit(placementOwner.placement.distance?.unit ?? null);
|
|
9579
10603
|
const metric = offsetPx / Math.max(FREE_DISTANCE_PIXEL_FACTOR * scene.scaleModel.freePlacementMultiplier, 1);
|
|
9580
10604
|
if (metric < 0.01) {
|
|
9581
|
-
|
|
9582
|
-
if (!
|
|
9583
|
-
delete
|
|
10605
|
+
placementOwner.placement.distance = void 0;
|
|
10606
|
+
if (!placementOwner.placement.descriptor) {
|
|
10607
|
+
delete placementOwner.placement.descriptor;
|
|
9584
10608
|
}
|
|
9585
10609
|
return next;
|
|
9586
10610
|
}
|
|
9587
|
-
|
|
9588
|
-
delete
|
|
10611
|
+
placementOwner.placement.distance = distanceMetricToUnitValue(metric, preferredUnit);
|
|
10612
|
+
delete placementOwner.placement.descriptor;
|
|
9589
10613
|
return next;
|
|
9590
10614
|
}
|
|
9591
10615
|
function findNearestSceneObject(scene, selectedObjectId, pointer, predicate = () => true) {
|
|
@@ -9857,9 +10881,60 @@
|
|
|
9857
10881
|
return ["viewpoint-zoom"];
|
|
9858
10882
|
case "rotationDeg":
|
|
9859
10883
|
return ["viewpoint-rotation"];
|
|
10884
|
+
case "events":
|
|
10885
|
+
return ["viewpoint-events"];
|
|
9860
10886
|
default:
|
|
9861
10887
|
return [];
|
|
9862
10888
|
}
|
|
10889
|
+
case "event":
|
|
10890
|
+
switch (field) {
|
|
10891
|
+
case "id":
|
|
10892
|
+
return ["event-id"];
|
|
10893
|
+
case "kind":
|
|
10894
|
+
return ["event-kind"];
|
|
10895
|
+
case "label":
|
|
10896
|
+
return ["event-label"];
|
|
10897
|
+
case "summary":
|
|
10898
|
+
return ["event-summary"];
|
|
10899
|
+
case "targetObjectId":
|
|
10900
|
+
case "target":
|
|
10901
|
+
return ["event-target"];
|
|
10902
|
+
case "participantObjectIds":
|
|
10903
|
+
case "participants":
|
|
10904
|
+
return ["event-participants"];
|
|
10905
|
+
case "timing":
|
|
10906
|
+
return ["event-timing"];
|
|
10907
|
+
case "visibility":
|
|
10908
|
+
return ["event-visibility"];
|
|
10909
|
+
case "tags":
|
|
10910
|
+
return ["event-tags"];
|
|
10911
|
+
case "color":
|
|
10912
|
+
return ["event-color"];
|
|
10913
|
+
case "hidden":
|
|
10914
|
+
return ["event-hidden"];
|
|
10915
|
+
default:
|
|
10916
|
+
return [];
|
|
10917
|
+
}
|
|
10918
|
+
case "event-pose":
|
|
10919
|
+
if (field === "objectId") {
|
|
10920
|
+
return ["pose-object-id"];
|
|
10921
|
+
}
|
|
10922
|
+
if (field === "placement") {
|
|
10923
|
+
return ["placement-mode"];
|
|
10924
|
+
}
|
|
10925
|
+
if (field === "reference" || field === "target") {
|
|
10926
|
+
return ["placement-target"];
|
|
10927
|
+
}
|
|
10928
|
+
if (field === "descriptor") {
|
|
10929
|
+
return ["placement-free"];
|
|
10930
|
+
}
|
|
10931
|
+
if (PLACEMENT_DIAGNOSTIC_FIELDS.has(field)) {
|
|
10932
|
+
return [`placement-${field}`];
|
|
10933
|
+
}
|
|
10934
|
+
if (field === "inner" || field === "outer") {
|
|
10935
|
+
return [`prop-${field}`];
|
|
10936
|
+
}
|
|
10937
|
+
return [];
|
|
9863
10938
|
case "annotation":
|
|
9864
10939
|
switch (field) {
|
|
9865
10940
|
case "id":
|
|
@@ -9945,6 +11020,10 @@
|
|
|
9945
11020
|
return `Metadata: ${path.key ?? ""}`;
|
|
9946
11021
|
case "group":
|
|
9947
11022
|
return `Group: ${path.id ?? ""}`;
|
|
11023
|
+
case "event":
|
|
11024
|
+
return `Event: ${path.id ?? ""}`;
|
|
11025
|
+
case "event-pose":
|
|
11026
|
+
return `Event Pose: ${path.id ?? ""} / ${path.key ?? ""}`;
|
|
9948
11027
|
case "object":
|
|
9949
11028
|
return `Object: ${path.id ?? ""}`;
|
|
9950
11029
|
case "viewpoint":
|
|
@@ -9956,11 +11035,70 @@
|
|
|
9956
11035
|
}
|
|
9957
11036
|
}
|
|
9958
11037
|
function selectionKey(path) {
|
|
9959
|
-
return path ? `${path.kind}:${path.id ?? path.key ?? ""}` : null;
|
|
11038
|
+
return path ? `${path.kind}:${path.id ?? ""}:${path.key ?? ""}` : null;
|
|
11039
|
+
}
|
|
11040
|
+
function selectionEventId(path) {
|
|
11041
|
+
if (!path) {
|
|
11042
|
+
return null;
|
|
11043
|
+
}
|
|
11044
|
+
return path.kind === "event" || path.kind === "event-pose" ? path.id ?? null : null;
|
|
9960
11045
|
}
|
|
9961
11046
|
function compareObjects2(left, right) {
|
|
9962
11047
|
return left.id.localeCompare(right.id);
|
|
9963
11048
|
}
|
|
11049
|
+
function compareEvents(left, right) {
|
|
11050
|
+
return left.id.localeCompare(right.id);
|
|
11051
|
+
}
|
|
11052
|
+
function compareEventPoses(left, right) {
|
|
11053
|
+
return left.objectId.localeCompare(right.objectId);
|
|
11054
|
+
}
|
|
11055
|
+
function findEvent2(document2, eventId) {
|
|
11056
|
+
return document2.events.find((entry) => entry.id === eventId) ?? null;
|
|
11057
|
+
}
|
|
11058
|
+
function findEventPose2(document2, eventId, objectId) {
|
|
11059
|
+
return findEvent2(document2, eventId)?.positions.find((entry) => entry.objectId === objectId) ?? null;
|
|
11060
|
+
}
|
|
11061
|
+
function findObject2(document2, objectId) {
|
|
11062
|
+
return document2.objects.find((entry) => entry.id === objectId) ?? null;
|
|
11063
|
+
}
|
|
11064
|
+
function addEventPose(document2, eventId) {
|
|
11065
|
+
const next = cloneAtlasDocument(document2);
|
|
11066
|
+
const eventEntry = next.events.find((entry) => entry.id === eventId);
|
|
11067
|
+
if (!eventEntry) {
|
|
11068
|
+
return document2;
|
|
11069
|
+
}
|
|
11070
|
+
const baseObject = next.objects.find((object) => !eventEntry.positions.some((pose) => pose.objectId === object.id)) ?? next.objects[0];
|
|
11071
|
+
if (!baseObject) {
|
|
11072
|
+
return document2;
|
|
11073
|
+
}
|
|
11074
|
+
if (eventEntry.targetObjectId !== baseObject.id && !eventEntry.participantObjectIds.includes(baseObject.id)) {
|
|
11075
|
+
eventEntry.participantObjectIds.push(baseObject.id);
|
|
11076
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
11077
|
+
}
|
|
11078
|
+
eventEntry.positions.push(createEventPoseFromObject(baseObject));
|
|
11079
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
11080
|
+
return next;
|
|
11081
|
+
}
|
|
11082
|
+
function createEventPoseFromObject(object) {
|
|
11083
|
+
return {
|
|
11084
|
+
objectId: object.id,
|
|
11085
|
+
placement: object.placement ? structuredClone(object.placement) : null,
|
|
11086
|
+
inner: readUnitValue(object.properties.inner),
|
|
11087
|
+
outer: readUnitValue(object.properties.outer)
|
|
11088
|
+
};
|
|
11089
|
+
}
|
|
11090
|
+
function syncEventViewpointReferences(document2, previousEventId, nextEventId, viewpointIds) {
|
|
11091
|
+
const desired = new Set(viewpointIds);
|
|
11092
|
+
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
11093
|
+
const currentIds = new Set(viewpoint.events);
|
|
11094
|
+
currentIds.delete(previousEventId);
|
|
11095
|
+
currentIds.delete(nextEventId);
|
|
11096
|
+
if (desired.has(viewpoint.id)) {
|
|
11097
|
+
currentIds.add(nextEventId);
|
|
11098
|
+
}
|
|
11099
|
+
viewpoint.events = [...currentIds].sort((left, right) => left.localeCompare(right));
|
|
11100
|
+
}
|
|
11101
|
+
}
|
|
9964
11102
|
function createUniqueId(prefix, existing) {
|
|
9965
11103
|
const safePrefix = prefix.trim() || "item";
|
|
9966
11104
|
let counter = 1;
|
|
@@ -9989,6 +11127,9 @@
|
|
|
9989
11127
|
function readUnitProperty(value) {
|
|
9990
11128
|
return value && typeof value === "object" && "value" in value ? formatUnitValue3(value) : "";
|
|
9991
11129
|
}
|
|
11130
|
+
function readUnitValue(value) {
|
|
11131
|
+
return value && typeof value === "object" && "value" in value ? value : void 0;
|
|
11132
|
+
}
|
|
9992
11133
|
function readNumberProperty(value) {
|
|
9993
11134
|
return typeof value === "number" ? String(value) : "";
|
|
9994
11135
|
}
|
|
@@ -10294,6 +11435,12 @@
|
|
|
10294
11435
|
.wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
|
|
10295
11436
|
.wo-editor-outline { display: grid; gap: 14px; }
|
|
10296
11437
|
.wo-editor-outline-section { display: grid; gap: 8px; }
|
|
11438
|
+
.wo-editor-outline-group { display: grid; gap: 6px; }
|
|
11439
|
+
.wo-editor-outline-children {
|
|
11440
|
+
display: grid;
|
|
11441
|
+
gap: 6px;
|
|
11442
|
+
padding-left: 16px;
|
|
11443
|
+
}
|
|
10297
11444
|
.wo-editor-outline-section h3 {
|
|
10298
11445
|
margin: 0;
|
|
10299
11446
|
color: rgba(237,246,255,0.68);
|
|
@@ -10333,6 +11480,27 @@
|
|
|
10333
11480
|
background: rgba(255, 120, 120, 0.18);
|
|
10334
11481
|
color: #ffb2b2;
|
|
10335
11482
|
}
|
|
11483
|
+
.wo-editor-inline-list { display: grid; gap: 8px; }
|
|
11484
|
+
.wo-editor-inline-actions {
|
|
11485
|
+
display: flex;
|
|
11486
|
+
flex-wrap: wrap;
|
|
11487
|
+
gap: 10px;
|
|
11488
|
+
margin-top: 12px;
|
|
11489
|
+
}
|
|
11490
|
+
.wo-editor-inline-actions button {
|
|
11491
|
+
border: 1px solid rgba(255,255,255,0.14);
|
|
11492
|
+
border-radius: 999px;
|
|
11493
|
+
background: rgba(255,255,255,0.06);
|
|
11494
|
+
color: #edf6ff;
|
|
11495
|
+
cursor: pointer;
|
|
11496
|
+
font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
11497
|
+
padding: 8px 12px;
|
|
11498
|
+
}
|
|
11499
|
+
.wo-editor-inline-note {
|
|
11500
|
+
margin: 0 0 12px;
|
|
11501
|
+
color: rgba(237,246,255,0.72);
|
|
11502
|
+
font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
11503
|
+
}
|
|
10336
11504
|
.wo-editor-diagnostics { display: grid; gap: 10px; }
|
|
10337
11505
|
.wo-editor-diagnostic {
|
|
10338
11506
|
display: grid;
|