worldorbit 2.5.16 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -15
- package/dist/browser/core/dist/index.js +1228 -110
- package/dist/browser/editor/dist/index.js +1896 -180
- package/dist/browser/markdown/dist/index.js +1071 -99
- package/dist/browser/viewer/dist/index.js +1127 -113
- package/dist/unpkg/core/dist/index.js +1228 -110
- package/dist/unpkg/editor/dist/index.js +1896 -180
- package/dist/unpkg/markdown/dist/index.js +1071 -99
- package/dist/unpkg/viewer/dist/index.js +1127 -113
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +295 -203
- package/dist/unpkg/worldorbit-markdown.min.js +66 -58
- package/dist/unpkg/worldorbit-viewer.min.js +84 -76
- package/dist/unpkg/worldorbit.js +1304 -124
- package/dist/unpkg/worldorbit.min.js +88 -80
- package/package.json +1 -1
- package/packages/core/dist/atlas-edit.js +75 -1
- package/packages/core/dist/atlas-validate.js +211 -8
- package/packages/core/dist/draft-parse.js +401 -22
- package/packages/core/dist/draft.d.ts +5 -2
- package/packages/core/dist/draft.js +103 -8
- package/packages/core/dist/format.js +99 -6
- package/packages/core/dist/load.js +9 -2
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +400 -64
- package/packages/core/dist/types.d.ts +60 -4
- package/packages/editor/dist/editor.js +702 -65
- package/packages/editor/dist/types.d.ts +3 -1
- package/packages/viewer/dist/atlas-state.js +11 -2
- package/packages/viewer/dist/atlas-viewer.js +19 -7
- 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 +12 -2
- package/packages/viewer/dist/viewer.js +28 -1
|
@@ -541,6 +541,7 @@
|
|
|
541
541
|
system,
|
|
542
542
|
groups: [],
|
|
543
543
|
relations: [],
|
|
544
|
+
events: [],
|
|
544
545
|
objects
|
|
545
546
|
};
|
|
546
547
|
}
|
|
@@ -920,12 +921,16 @@
|
|
|
920
921
|
const height = frame.height;
|
|
921
922
|
const padding = frame.padding;
|
|
922
923
|
const layoutPreset = resolveLayoutPreset(document2);
|
|
923
|
-
const
|
|
924
|
+
const schemaProjection = resolveProjection(document2, options.projection);
|
|
925
|
+
const camera = normalizeViewCamera(options.camera ?? null);
|
|
926
|
+
const renderProjection = resolveRenderProjection(schemaProjection, camera);
|
|
924
927
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
925
928
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
926
929
|
const systemId = document2.system?.id ?? null;
|
|
927
|
-
const
|
|
928
|
-
const
|
|
930
|
+
const activeEventId = options.activeEventId ?? null;
|
|
931
|
+
const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
|
|
932
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
933
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
929
934
|
const positions = /* @__PURE__ */ new Map();
|
|
930
935
|
const orbitDrafts = [];
|
|
931
936
|
const leaderDrafts = [];
|
|
@@ -934,7 +939,7 @@
|
|
|
934
939
|
const atObjects = [];
|
|
935
940
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
936
941
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
937
|
-
for (const object of
|
|
942
|
+
for (const object of effectiveObjects) {
|
|
938
943
|
const placement = object.placement;
|
|
939
944
|
if (!placement) {
|
|
940
945
|
rootObjects.push(object);
|
|
@@ -961,7 +966,7 @@
|
|
|
961
966
|
surfaceChildren,
|
|
962
967
|
objectMap,
|
|
963
968
|
spacingFactor,
|
|
964
|
-
projection,
|
|
969
|
+
projection: renderProjection,
|
|
965
970
|
scaleModel
|
|
966
971
|
};
|
|
967
972
|
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
@@ -973,7 +978,7 @@
|
|
|
973
978
|
const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
974
979
|
secondaryRoots.forEach((object, index) => {
|
|
975
980
|
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
976
|
-
const offset = projectPolarOffset(angle, rootRingRadius,
|
|
981
|
+
const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
|
|
977
982
|
placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
978
983
|
});
|
|
979
984
|
}
|
|
@@ -1029,38 +1034,48 @@
|
|
|
1029
1034
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1030
1035
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1031
1036
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1032
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1037
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1033
1038
|
const relations = createSceneRelations(document2, objects);
|
|
1034
|
-
const
|
|
1035
|
-
const
|
|
1039
|
+
const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
|
|
1040
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1041
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1036
1042
|
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1037
|
-
const viewpoints = createSceneViewpoints(document2,
|
|
1038
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1043
|
+
const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
|
|
1044
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1039
1045
|
return {
|
|
1040
1046
|
width,
|
|
1041
1047
|
height,
|
|
1042
1048
|
padding,
|
|
1043
1049
|
renderPreset: frame.preset,
|
|
1044
|
-
projection,
|
|
1050
|
+
projection: schemaProjection,
|
|
1051
|
+
renderProjection,
|
|
1052
|
+
camera,
|
|
1045
1053
|
scaleModel,
|
|
1046
1054
|
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1047
|
-
subtitle:
|
|
1055
|
+
subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
|
|
1048
1056
|
systemId,
|
|
1049
|
-
viewMode:
|
|
1057
|
+
viewMode: schemaProjection,
|
|
1050
1058
|
layoutPreset,
|
|
1051
1059
|
metadata: {
|
|
1052
1060
|
format: document2.format,
|
|
1053
1061
|
version: document2.version,
|
|
1054
|
-
view:
|
|
1062
|
+
view: schemaProjection,
|
|
1063
|
+
renderProjection,
|
|
1055
1064
|
scale: String(document2.system?.properties.scale ?? layoutPreset),
|
|
1056
1065
|
units: String(document2.system?.properties.units ?? "mixed"),
|
|
1057
|
-
preset: frame.preset ?? "custom"
|
|
1066
|
+
preset: frame.preset ?? "custom",
|
|
1067
|
+
...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
|
|
1068
|
+
...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
|
|
1069
|
+
...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
|
|
1070
|
+
...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
|
|
1058
1071
|
},
|
|
1059
1072
|
contentBounds,
|
|
1060
1073
|
layers,
|
|
1061
1074
|
groups,
|
|
1062
1075
|
semanticGroups,
|
|
1063
1076
|
viewpoints,
|
|
1077
|
+
events,
|
|
1078
|
+
activeEventId,
|
|
1064
1079
|
objects,
|
|
1065
1080
|
orbitVisuals,
|
|
1066
1081
|
relations,
|
|
@@ -1079,6 +1094,56 @@
|
|
|
1079
1094
|
y: center.y + dx * sin + dy * cos
|
|
1080
1095
|
};
|
|
1081
1096
|
}
|
|
1097
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1098
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1099
|
+
if (!activeEventId) {
|
|
1100
|
+
return cloned;
|
|
1101
|
+
}
|
|
1102
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1103
|
+
if (!activeEvent) {
|
|
1104
|
+
return cloned;
|
|
1105
|
+
}
|
|
1106
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1107
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
1108
|
+
...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
|
|
1109
|
+
...activeEvent.participantObjectIds,
|
|
1110
|
+
...activeEvent.positions.map((pose) => pose.objectId)
|
|
1111
|
+
]);
|
|
1112
|
+
for (const objectId of referencedIds) {
|
|
1113
|
+
const object = objectMap.get(objectId);
|
|
1114
|
+
if (!object) {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (activeEvent.epoch) {
|
|
1118
|
+
object.epoch = activeEvent.epoch;
|
|
1119
|
+
}
|
|
1120
|
+
if (activeEvent.referencePlane) {
|
|
1121
|
+
object.referencePlane = activeEvent.referencePlane;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
for (const pose of activeEvent.positions) {
|
|
1125
|
+
const object = objectMap.get(pose.objectId);
|
|
1126
|
+
if (!object) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (pose.placement) {
|
|
1130
|
+
object.placement = structuredClone(pose.placement);
|
|
1131
|
+
}
|
|
1132
|
+
if (pose.inner) {
|
|
1133
|
+
object.properties.inner = { ...pose.inner };
|
|
1134
|
+
}
|
|
1135
|
+
if (pose.outer) {
|
|
1136
|
+
object.properties.outer = { ...pose.outer };
|
|
1137
|
+
}
|
|
1138
|
+
if (pose.epoch) {
|
|
1139
|
+
object.epoch = pose.epoch;
|
|
1140
|
+
}
|
|
1141
|
+
if (pose.referencePlane) {
|
|
1142
|
+
object.referencePlane = pose.referencePlane;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return cloned;
|
|
1146
|
+
}
|
|
1082
1147
|
function resolveLayoutPreset(document2) {
|
|
1083
1148
|
const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1084
1149
|
switch (rawScale) {
|
|
@@ -1115,10 +1180,59 @@
|
|
|
1115
1180
|
}
|
|
1116
1181
|
}
|
|
1117
1182
|
function resolveProjection(document2, projection) {
|
|
1118
|
-
if (projection === "topdown" || projection === "isometric") {
|
|
1183
|
+
if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
|
|
1119
1184
|
return projection;
|
|
1120
1185
|
}
|
|
1121
|
-
|
|
1186
|
+
const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
|
|
1187
|
+
return parseViewProjection(documentView) ?? "topdown";
|
|
1188
|
+
}
|
|
1189
|
+
function resolveRenderProjection(projection, camera) {
|
|
1190
|
+
switch (projection) {
|
|
1191
|
+
case "topdown":
|
|
1192
|
+
return "topdown";
|
|
1193
|
+
case "isometric":
|
|
1194
|
+
return "isometric";
|
|
1195
|
+
case "orthographic":
|
|
1196
|
+
return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
|
|
1197
|
+
case "perspective":
|
|
1198
|
+
return "isometric";
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function normalizeViewCamera(camera) {
|
|
1202
|
+
if (!camera) {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
const normalized = {
|
|
1206
|
+
azimuth: normalizeFiniteCameraValue(camera.azimuth),
|
|
1207
|
+
elevation: normalizeFiniteCameraValue(camera.elevation),
|
|
1208
|
+
roll: normalizeFiniteCameraValue(camera.roll),
|
|
1209
|
+
distance: normalizePositiveCameraDistance(camera.distance)
|
|
1210
|
+
};
|
|
1211
|
+
return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
|
|
1212
|
+
}
|
|
1213
|
+
function normalizeFiniteCameraValue(value) {
|
|
1214
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1215
|
+
}
|
|
1216
|
+
function normalizePositiveCameraDistance(value) {
|
|
1217
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
1218
|
+
}
|
|
1219
|
+
function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
|
|
1220
|
+
const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
|
|
1221
|
+
if (projection !== renderProjection) {
|
|
1222
|
+
parts.push(`2D ${renderProjection} fallback`);
|
|
1223
|
+
}
|
|
1224
|
+
if (camera) {
|
|
1225
|
+
const cameraParts = [
|
|
1226
|
+
camera.azimuth !== null ? `az ${camera.azimuth}` : null,
|
|
1227
|
+
camera.elevation !== null ? `el ${camera.elevation}` : null,
|
|
1228
|
+
camera.roll !== null ? `roll ${camera.roll}` : null,
|
|
1229
|
+
camera.distance !== null ? `dist ${camera.distance}` : null
|
|
1230
|
+
].filter(Boolean);
|
|
1231
|
+
if (cameraParts.length > 0) {
|
|
1232
|
+
parts.push(`camera ${cameraParts.join(" / ")}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return parts.join(" - ");
|
|
1122
1236
|
}
|
|
1123
1237
|
function resolveScaleModel(layoutPreset, overrides) {
|
|
1124
1238
|
const defaults = defaultScaleModel(layoutPreset);
|
|
@@ -1234,24 +1348,14 @@
|
|
|
1234
1348
|
hidden: draft.object.properties.hidden === true
|
|
1235
1349
|
};
|
|
1236
1350
|
}
|
|
1237
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1351
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1238
1352
|
const labels = [];
|
|
1239
1353
|
const occupied = [];
|
|
1240
|
-
const
|
|
1354
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1355
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1241
1356
|
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);
|
|
1357
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1358
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1255
1359
|
labels.push({
|
|
1256
1360
|
renderId: `${object.renderId}-label`,
|
|
1257
1361
|
objectId: object.objectId,
|
|
@@ -1260,17 +1364,128 @@
|
|
|
1260
1364
|
semanticGroupIds: [...object.semanticGroupIds],
|
|
1261
1365
|
label: object.label,
|
|
1262
1366
|
secondaryLabel: object.secondaryLabel,
|
|
1263
|
-
x:
|
|
1264
|
-
y: labelY,
|
|
1265
|
-
secondaryY,
|
|
1266
|
-
textAnchor:
|
|
1267
|
-
direction: direction
|
|
1367
|
+
x: placement.x,
|
|
1368
|
+
y: placement.labelY,
|
|
1369
|
+
secondaryY: placement.secondaryY,
|
|
1370
|
+
textAnchor: placement.textAnchor,
|
|
1371
|
+
direction: placement.direction,
|
|
1268
1372
|
hidden: object.hidden
|
|
1269
1373
|
});
|
|
1270
1374
|
}
|
|
1271
1375
|
return labels;
|
|
1272
1376
|
}
|
|
1273
|
-
function
|
|
1377
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1378
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1379
|
+
if (priorityDiff !== 0) {
|
|
1380
|
+
return priorityDiff;
|
|
1381
|
+
}
|
|
1382
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1383
|
+
if (renderPriorityDiff !== 0) {
|
|
1384
|
+
return renderPriorityDiff;
|
|
1385
|
+
}
|
|
1386
|
+
return left.sortKey - right.sortKey;
|
|
1387
|
+
}
|
|
1388
|
+
function labelPlacementPriority(object) {
|
|
1389
|
+
switch (object.object.type) {
|
|
1390
|
+
case "star":
|
|
1391
|
+
return 0;
|
|
1392
|
+
case "planet":
|
|
1393
|
+
return 1;
|
|
1394
|
+
case "moon":
|
|
1395
|
+
return 2;
|
|
1396
|
+
case "belt":
|
|
1397
|
+
case "ring":
|
|
1398
|
+
return 3;
|
|
1399
|
+
case "asteroid":
|
|
1400
|
+
case "comet":
|
|
1401
|
+
return 4;
|
|
1402
|
+
case "structure":
|
|
1403
|
+
case "phenomenon":
|
|
1404
|
+
return 5;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1408
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1409
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1410
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1411
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1412
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1413
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1414
|
+
return placement;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1421
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1422
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1423
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1424
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1425
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1426
|
+
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";
|
|
1427
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1428
|
+
}
|
|
1429
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1430
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1431
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1432
|
+
}
|
|
1433
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1434
|
+
}
|
|
1435
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1436
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1437
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1438
|
+
}
|
|
1439
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1440
|
+
}
|
|
1441
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1442
|
+
const step = 14 * labelMultiplier;
|
|
1443
|
+
switch (direction) {
|
|
1444
|
+
case "above": {
|
|
1445
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1446
|
+
return {
|
|
1447
|
+
x: object.x,
|
|
1448
|
+
labelY,
|
|
1449
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1450
|
+
textAnchor: "middle",
|
|
1451
|
+
direction
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
case "below": {
|
|
1455
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1456
|
+
return {
|
|
1457
|
+
x: object.x,
|
|
1458
|
+
labelY,
|
|
1459
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1460
|
+
textAnchor: "middle",
|
|
1461
|
+
direction
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
case "left": {
|
|
1465
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1466
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1467
|
+
return {
|
|
1468
|
+
x,
|
|
1469
|
+
labelY,
|
|
1470
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1471
|
+
textAnchor: "end",
|
|
1472
|
+
direction
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
case "right": {
|
|
1476
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1477
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1478
|
+
return {
|
|
1479
|
+
x,
|
|
1480
|
+
labelY,
|
|
1481
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1482
|
+
textAnchor: "start",
|
|
1483
|
+
direction
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1274
1489
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1275
1490
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1276
1491
|
return [
|
|
@@ -1285,6 +1500,10 @@
|
|
|
1285
1500
|
id: "relations",
|
|
1286
1501
|
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1287
1502
|
},
|
|
1503
|
+
{
|
|
1504
|
+
id: "events",
|
|
1505
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1506
|
+
},
|
|
1288
1507
|
{
|
|
1289
1508
|
id: "objects",
|
|
1290
1509
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1296,7 +1515,7 @@
|
|
|
1296
1515
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1297
1516
|
];
|
|
1298
1517
|
}
|
|
1299
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1518
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1300
1519
|
const groups = /* @__PURE__ */ new Map();
|
|
1301
1520
|
const ensureGroup = (groupId) => {
|
|
1302
1521
|
if (!groupId) {
|
|
@@ -1345,7 +1564,7 @@
|
|
|
1345
1564
|
}
|
|
1346
1565
|
}
|
|
1347
1566
|
for (const group of groups.values()) {
|
|
1348
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1567
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1349
1568
|
}
|
|
1350
1569
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1351
1570
|
}
|
|
@@ -1379,6 +1598,29 @@
|
|
|
1379
1598
|
};
|
|
1380
1599
|
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1381
1600
|
}
|
|
1601
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1602
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1603
|
+
return events.map((event) => {
|
|
1604
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1605
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1606
|
+
...event.participantObjectIds
|
|
1607
|
+
])];
|
|
1608
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1609
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1610
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1611
|
+
return {
|
|
1612
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1613
|
+
eventId: event.id,
|
|
1614
|
+
event,
|
|
1615
|
+
objectIds,
|
|
1616
|
+
participantIds: [...event.participantObjectIds],
|
|
1617
|
+
targetObjectId: event.targetObjectId,
|
|
1618
|
+
x: centroidX,
|
|
1619
|
+
y: centroidY,
|
|
1620
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1621
|
+
};
|
|
1622
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1623
|
+
}
|
|
1382
1624
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1383
1625
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1384
1626
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1426,13 +1668,18 @@
|
|
|
1426
1668
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1427
1669
|
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1428
1670
|
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1671
|
+
const camera = normalizeViewCamera(null);
|
|
1672
|
+
const renderProjection = resolveRenderProjection(projection, camera);
|
|
1429
1673
|
return {
|
|
1430
1674
|
id: "overview",
|
|
1431
1675
|
label,
|
|
1432
1676
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1433
1677
|
objectId: null,
|
|
1434
1678
|
selectedObjectId: null,
|
|
1679
|
+
eventIds: [],
|
|
1435
1680
|
projection,
|
|
1681
|
+
renderProjection,
|
|
1682
|
+
camera,
|
|
1436
1683
|
preset,
|
|
1437
1684
|
rotationDeg: 0,
|
|
1438
1685
|
scale: null,
|
|
@@ -1468,6 +1715,9 @@
|
|
|
1468
1715
|
draft.select = normalizedValue;
|
|
1469
1716
|
}
|
|
1470
1717
|
return;
|
|
1718
|
+
case "events":
|
|
1719
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
1720
|
+
return;
|
|
1471
1721
|
case "projection":
|
|
1472
1722
|
case "view":
|
|
1473
1723
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1479,6 +1729,30 @@
|
|
|
1479
1729
|
case "angle":
|
|
1480
1730
|
draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
|
|
1481
1731
|
return;
|
|
1732
|
+
case "camera.azimuth":
|
|
1733
|
+
draft.camera = {
|
|
1734
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1735
|
+
azimuth: parseFiniteNumber(normalizedValue)
|
|
1736
|
+
};
|
|
1737
|
+
return;
|
|
1738
|
+
case "camera.elevation":
|
|
1739
|
+
draft.camera = {
|
|
1740
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1741
|
+
elevation: parseFiniteNumber(normalizedValue)
|
|
1742
|
+
};
|
|
1743
|
+
return;
|
|
1744
|
+
case "camera.roll":
|
|
1745
|
+
draft.camera = {
|
|
1746
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1747
|
+
roll: parseFiniteNumber(normalizedValue)
|
|
1748
|
+
};
|
|
1749
|
+
return;
|
|
1750
|
+
case "camera.distance":
|
|
1751
|
+
draft.camera = {
|
|
1752
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1753
|
+
distance: parsePositiveNumber(normalizedValue)
|
|
1754
|
+
};
|
|
1755
|
+
return;
|
|
1482
1756
|
case "zoom":
|
|
1483
1757
|
case "scale":
|
|
1484
1758
|
draft.scale = parsePositiveNumber(normalizedValue);
|
|
@@ -1518,13 +1792,19 @@
|
|
|
1518
1792
|
const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
|
|
1519
1793
|
const filter = normalizeViewpointFilter(draft.filter);
|
|
1520
1794
|
const label = draft.label?.trim() || humanizeIdentifier(draft.id);
|
|
1795
|
+
const resolvedProjection = draft.projection ?? projection;
|
|
1796
|
+
const camera = normalizeViewCamera(draft.camera ?? null);
|
|
1797
|
+
const renderProjection = resolveRenderProjection(resolvedProjection, camera);
|
|
1521
1798
|
return {
|
|
1522
1799
|
id: draft.id,
|
|
1523
1800
|
label,
|
|
1524
1801
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1525
1802
|
objectId,
|
|
1526
1803
|
selectedObjectId,
|
|
1527
|
-
|
|
1804
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
1805
|
+
projection: resolvedProjection,
|
|
1806
|
+
renderProjection,
|
|
1807
|
+
camera,
|
|
1528
1808
|
preset: draft.preset ?? preset,
|
|
1529
1809
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
1530
1810
|
scale: draft.scale ?? null,
|
|
@@ -1541,6 +1821,14 @@
|
|
|
1541
1821
|
groupIds: []
|
|
1542
1822
|
};
|
|
1543
1823
|
}
|
|
1824
|
+
function createEmptyViewCamera() {
|
|
1825
|
+
return {
|
|
1826
|
+
azimuth: null,
|
|
1827
|
+
elevation: null,
|
|
1828
|
+
roll: null,
|
|
1829
|
+
distance: null
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1544
1832
|
function normalizeViewpointFilter(filter) {
|
|
1545
1833
|
if (!filter) {
|
|
1546
1834
|
return null;
|
|
@@ -1554,7 +1842,18 @@
|
|
|
1554
1842
|
return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
|
|
1555
1843
|
}
|
|
1556
1844
|
function parseViewProjection(value) {
|
|
1557
|
-
|
|
1845
|
+
switch (value.toLowerCase()) {
|
|
1846
|
+
case "topdown":
|
|
1847
|
+
return "topdown";
|
|
1848
|
+
case "isometric":
|
|
1849
|
+
return "isometric";
|
|
1850
|
+
case "orthographic":
|
|
1851
|
+
return "orthographic";
|
|
1852
|
+
case "perspective":
|
|
1853
|
+
return "perspective";
|
|
1854
|
+
default:
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1558
1857
|
}
|
|
1559
1858
|
function parseRenderPreset(value) {
|
|
1560
1859
|
const normalized = value.toLowerCase();
|
|
@@ -1581,7 +1880,7 @@
|
|
|
1581
1880
|
next["orbits-front"] = enabled;
|
|
1582
1881
|
continue;
|
|
1583
1882
|
}
|
|
1584
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1883
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1585
1884
|
next[rawLayer] = enabled;
|
|
1586
1885
|
}
|
|
1587
1886
|
}
|
|
@@ -1592,7 +1891,7 @@
|
|
|
1592
1891
|
}
|
|
1593
1892
|
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1594
1893
|
return splitListValue(value).map((entry) => {
|
|
1595
|
-
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
1894
|
+
if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
|
|
1596
1895
|
return entry;
|
|
1597
1896
|
}
|
|
1598
1897
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
@@ -1629,7 +1928,7 @@
|
|
|
1629
1928
|
}
|
|
1630
1929
|
return parts.join(" - ");
|
|
1631
1930
|
}
|
|
1632
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
1931
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1633
1932
|
let minX = Number.POSITIVE_INFINITY;
|
|
1634
1933
|
let minY = Number.POSITIVE_INFINITY;
|
|
1635
1934
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1659,7 +1958,7 @@
|
|
|
1659
1958
|
for (const label of labels) {
|
|
1660
1959
|
if (label.hidden)
|
|
1661
1960
|
continue;
|
|
1662
|
-
includeLabelBounds(label, include);
|
|
1961
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1663
1962
|
}
|
|
1664
1963
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1665
1964
|
return createBounds(0, 0, width, height);
|
|
@@ -1697,13 +1996,10 @@
|
|
|
1697
1996
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1698
1997
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1699
1998
|
}
|
|
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);
|
|
1999
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
2000
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
2001
|
+
include(bounds.left, bounds.top);
|
|
2002
|
+
include(bounds.right, bounds.bottom);
|
|
1707
2003
|
}
|
|
1708
2004
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1709
2005
|
if (positions.has(object.id)) {
|
|
@@ -2093,7 +2389,7 @@
|
|
|
2093
2389
|
return null;
|
|
2094
2390
|
}
|
|
2095
2391
|
}
|
|
2096
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2392
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2097
2393
|
let minX = Number.POSITIVE_INFINITY;
|
|
2098
2394
|
let minY = Number.POSITIVE_INFINITY;
|
|
2099
2395
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2122,7 +2418,7 @@
|
|
|
2122
2418
|
}
|
|
2123
2419
|
for (const label of labels) {
|
|
2124
2420
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2125
|
-
includeLabelBounds(label, include);
|
|
2421
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2126
2422
|
}
|
|
2127
2423
|
}
|
|
2128
2424
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2147,12 +2443,28 @@
|
|
|
2147
2443
|
}
|
|
2148
2444
|
return current.id;
|
|
2149
2445
|
}
|
|
2150
|
-
function createLabelRect(
|
|
2446
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2447
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2448
|
+
}
|
|
2449
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2450
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2451
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2452
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2453
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2454
|
+
let left = x - labelHalfWidth;
|
|
2455
|
+
let right = x + labelHalfWidth;
|
|
2456
|
+
if (textAnchor === "start") {
|
|
2457
|
+
left = x;
|
|
2458
|
+
right = x + labelWidth;
|
|
2459
|
+
} else if (textAnchor === "end") {
|
|
2460
|
+
left = x - labelWidth;
|
|
2461
|
+
right = x;
|
|
2462
|
+
}
|
|
2151
2463
|
return {
|
|
2152
|
-
left
|
|
2153
|
-
right
|
|
2154
|
-
top: Math.min(labelY, secondaryY) -
|
|
2155
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2464
|
+
left,
|
|
2465
|
+
right,
|
|
2466
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2467
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2156
2468
|
};
|
|
2157
2469
|
}
|
|
2158
2470
|
function rectsOverlap(left, right) {
|
|
@@ -2339,11 +2651,6 @@
|
|
|
2339
2651
|
function customColorFor(value) {
|
|
2340
2652
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2341
2653
|
}
|
|
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
2654
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2348
2655
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2349
2656
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2377,12 +2684,13 @@
|
|
|
2377
2684
|
}
|
|
2378
2685
|
return {
|
|
2379
2686
|
format: "worldorbit",
|
|
2380
|
-
version: "2.
|
|
2381
|
-
schemaVersion: "2.
|
|
2687
|
+
version: "2.5",
|
|
2688
|
+
schemaVersion: "2.5",
|
|
2382
2689
|
sourceVersion: document2.version,
|
|
2383
2690
|
system,
|
|
2384
2691
|
groups: structuredClone(document2.groups ?? []),
|
|
2385
2692
|
relations: structuredClone(document2.relations ?? []),
|
|
2693
|
+
events: structuredClone(document2.events ?? []),
|
|
2386
2694
|
objects: document2.objects.map(cloneWorldOrbitObject),
|
|
2387
2695
|
diagnostics
|
|
2388
2696
|
};
|
|
@@ -2390,7 +2698,7 @@
|
|
|
2390
2698
|
function upgradeDocumentToDraftV2(document2, options = {}) {
|
|
2391
2699
|
return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document2, options));
|
|
2392
2700
|
}
|
|
2393
|
-
function materializeAtlasDocument(document2) {
|
|
2701
|
+
function materializeAtlasDocument(document2, options = {}) {
|
|
2394
2702
|
const system = document2.system ? {
|
|
2395
2703
|
type: "system",
|
|
2396
2704
|
id: document2.system.id,
|
|
@@ -2401,6 +2709,8 @@
|
|
|
2401
2709
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2402
2710
|
info: materializeDraftSystemInfo(document2.system)
|
|
2403
2711
|
} : null;
|
|
2712
|
+
const objects = document2.objects.map(cloneWorldOrbitObject);
|
|
2713
|
+
applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
|
|
2404
2714
|
return {
|
|
2405
2715
|
format: "worldorbit",
|
|
2406
2716
|
version: "1.0",
|
|
@@ -2408,7 +2718,8 @@
|
|
|
2408
2718
|
system,
|
|
2409
2719
|
groups: structuredClone(document2.groups ?? []),
|
|
2410
2720
|
relations: structuredClone(document2.relations ?? []),
|
|
2411
|
-
|
|
2721
|
+
events: document2.events.map(cloneWorldOrbitEvent),
|
|
2722
|
+
objects
|
|
2412
2723
|
};
|
|
2413
2724
|
}
|
|
2414
2725
|
function createDraftSystem(document2, defaults, atlasMetadata, annotations, diagnostics, preset) {
|
|
@@ -2430,8 +2741,9 @@
|
|
|
2430
2741
|
};
|
|
2431
2742
|
}
|
|
2432
2743
|
function createDraftDefaults(document2, preset, projection) {
|
|
2744
|
+
const rawView = typeof document2.system?.properties.view === "string" ? document2.system.properties.view.toLowerCase() : null;
|
|
2433
2745
|
return {
|
|
2434
|
-
view:
|
|
2746
|
+
view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
|
|
2435
2747
|
scale: typeof document2.system?.properties.scale === "string" ? document2.system.properties.scale : null,
|
|
2436
2748
|
units: typeof document2.system?.properties.units === "string" ? document2.system.properties.units : null,
|
|
2437
2749
|
preset,
|
|
@@ -2533,10 +2845,12 @@
|
|
|
2533
2845
|
summary: viewpoint.summary,
|
|
2534
2846
|
focusObjectId: viewpoint.objectId,
|
|
2535
2847
|
selectedObjectId: viewpoint.selectedObjectId,
|
|
2848
|
+
events: [...viewpoint.eventIds],
|
|
2536
2849
|
projection: viewpoint.projection,
|
|
2537
2850
|
preset: viewpoint.preset,
|
|
2538
2851
|
zoom: viewpoint.scale,
|
|
2539
2852
|
rotationDeg: viewpoint.rotationDeg,
|
|
2853
|
+
camera: viewpoint.camera ? { ...viewpoint.camera } : null,
|
|
2540
2854
|
layers: { ...viewpoint.layers },
|
|
2541
2855
|
filter: viewpoint.filter ? {
|
|
2542
2856
|
query: viewpoint.filter.query,
|
|
@@ -2565,6 +2879,75 @@
|
|
|
2565
2879
|
info: { ...object.info }
|
|
2566
2880
|
};
|
|
2567
2881
|
}
|
|
2882
|
+
function cloneWorldOrbitEvent(event) {
|
|
2883
|
+
return {
|
|
2884
|
+
...event,
|
|
2885
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
2886
|
+
tags: [...event.tags],
|
|
2887
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
2891
|
+
return {
|
|
2892
|
+
objectId: pose.objectId,
|
|
2893
|
+
placement: clonePlacement(pose.placement),
|
|
2894
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
2895
|
+
outer: pose.outer ? { ...pose.outer } : void 0,
|
|
2896
|
+
epoch: pose.epoch ?? null,
|
|
2897
|
+
referencePlane: pose.referencePlane ?? null
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
function clonePlacement(placement) {
|
|
2901
|
+
return placement ? structuredClone(placement) : null;
|
|
2902
|
+
}
|
|
2903
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
2904
|
+
if (!activeEventId) {
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
2908
|
+
if (!event) {
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
2912
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
2913
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
2914
|
+
...event.participantObjectIds,
|
|
2915
|
+
...event.positions.map((pose) => pose.objectId)
|
|
2916
|
+
]);
|
|
2917
|
+
for (const objectId of referencedIds) {
|
|
2918
|
+
const object = objectMap.get(objectId);
|
|
2919
|
+
if (!object) {
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (event.epoch) {
|
|
2923
|
+
object.epoch = event.epoch;
|
|
2924
|
+
}
|
|
2925
|
+
if (event.referencePlane) {
|
|
2926
|
+
object.referencePlane = event.referencePlane;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
for (const pose of event.positions) {
|
|
2930
|
+
const object = objectMap.get(pose.objectId);
|
|
2931
|
+
if (!object) {
|
|
2932
|
+
continue;
|
|
2933
|
+
}
|
|
2934
|
+
if (pose.placement) {
|
|
2935
|
+
object.placement = clonePlacement(pose.placement);
|
|
2936
|
+
}
|
|
2937
|
+
if (pose.inner) {
|
|
2938
|
+
object.properties.inner = { ...pose.inner };
|
|
2939
|
+
}
|
|
2940
|
+
if (pose.outer) {
|
|
2941
|
+
object.properties.outer = { ...pose.outer };
|
|
2942
|
+
}
|
|
2943
|
+
if (pose.epoch) {
|
|
2944
|
+
object.epoch = pose.epoch;
|
|
2945
|
+
}
|
|
2946
|
+
if (pose.referencePlane) {
|
|
2947
|
+
object.referencePlane = pose.referencePlane;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2568
2951
|
function cloneProperties(properties) {
|
|
2569
2952
|
const next = {};
|
|
2570
2953
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2646,6 +3029,18 @@
|
|
|
2646
3029
|
if (viewpoint.rotationDeg !== 0) {
|
|
2647
3030
|
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2648
3031
|
}
|
|
3032
|
+
if (viewpoint.camera?.azimuth !== null) {
|
|
3033
|
+
info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
|
|
3034
|
+
}
|
|
3035
|
+
if (viewpoint.camera?.elevation !== null) {
|
|
3036
|
+
info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
|
|
3037
|
+
}
|
|
3038
|
+
if (viewpoint.camera?.roll !== null) {
|
|
3039
|
+
info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
|
|
3040
|
+
}
|
|
3041
|
+
if (viewpoint.camera?.distance !== null) {
|
|
3042
|
+
info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
|
|
3043
|
+
}
|
|
2649
3044
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2650
3045
|
if (serializedLayers) {
|
|
2651
3046
|
info2[`${prefix}.layers`] = serializedLayers;
|
|
@@ -2662,6 +3057,9 @@
|
|
|
2662
3057
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2663
3058
|
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2664
3059
|
}
|
|
3060
|
+
if (viewpoint.events.length > 0) {
|
|
3061
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
3062
|
+
}
|
|
2665
3063
|
}
|
|
2666
3064
|
for (const annotation of system.annotations) {
|
|
2667
3065
|
const prefix = `annotation.${annotation.id}`;
|
|
@@ -2686,7 +3084,7 @@
|
|
|
2686
3084
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2687
3085
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2688
3086
|
}
|
|
2689
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3087
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2690
3088
|
if (layers[key] !== void 0) {
|
|
2691
3089
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2692
3090
|
}
|
|
@@ -2739,26 +3137,26 @@
|
|
|
2739
3137
|
];
|
|
2740
3138
|
function formatDocument(document2, options = {}) {
|
|
2741
3139
|
const schema = options.schema ?? "auto";
|
|
2742
|
-
const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.0-draft";
|
|
3140
|
+
const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.0-draft";
|
|
2743
3141
|
if (useDraft) {
|
|
2744
3142
|
if (schema === "2.0-draft") {
|
|
2745
|
-
const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" ? {
|
|
3143
|
+
const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
|
|
2746
3144
|
...document2,
|
|
2747
3145
|
version: "2.0-draft",
|
|
2748
3146
|
schemaVersion: "2.0-draft"
|
|
2749
3147
|
} : upgradeDocumentToDraftV2(document2);
|
|
2750
3148
|
return formatDraftDocument(legacyDraftDocument);
|
|
2751
3149
|
}
|
|
2752
|
-
const atlasDocument = document2.version === "2.0" || document2.version === "2.1" ? document2 : document2.version === "2.0-draft" ? {
|
|
3150
|
+
const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
|
|
2753
3151
|
...document2,
|
|
2754
3152
|
version: "2.0",
|
|
2755
3153
|
schemaVersion: "2.0"
|
|
2756
3154
|
} : upgradeDocumentToV2(document2);
|
|
2757
|
-
if (schema === "2.1" && atlasDocument.version !==
|
|
3155
|
+
if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
|
|
2758
3156
|
return formatAtlasDocument({
|
|
2759
3157
|
...atlasDocument,
|
|
2760
|
-
version:
|
|
2761
|
-
schemaVersion:
|
|
3158
|
+
version: schema,
|
|
3159
|
+
schemaVersion: schema
|
|
2762
3160
|
});
|
|
2763
3161
|
}
|
|
2764
3162
|
return formatAtlasDocument(atlasDocument);
|
|
@@ -2790,6 +3188,10 @@
|
|
|
2790
3188
|
lines.push("");
|
|
2791
3189
|
lines.push(...formatAtlasRelation(relation));
|
|
2792
3190
|
}
|
|
3191
|
+
for (const event of [...document2.events].sort(compareIdLike)) {
|
|
3192
|
+
lines.push("");
|
|
3193
|
+
lines.push(...formatAtlasEvent(event));
|
|
3194
|
+
}
|
|
2793
3195
|
const sortedObjects = [...document2.objects].sort(compareObjects);
|
|
2794
3196
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2795
3197
|
lines.push("");
|
|
@@ -2820,6 +3222,10 @@
|
|
|
2820
3222
|
lines.push("");
|
|
2821
3223
|
lines.push(...formatAtlasRelation(relation));
|
|
2822
3224
|
}
|
|
3225
|
+
for (const event of [...legacy.events].sort(compareIdLike)) {
|
|
3226
|
+
lines.push("");
|
|
3227
|
+
lines.push(...formatAtlasEvent(event));
|
|
3228
|
+
}
|
|
2823
3229
|
const sortedObjects = [...legacy.objects].sort(compareObjects);
|
|
2824
3230
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2825
3231
|
lines.push("");
|
|
@@ -3027,10 +3433,28 @@
|
|
|
3027
3433
|
if (viewpoint.rotationDeg !== 0) {
|
|
3028
3434
|
lines.push(` rotation ${viewpoint.rotationDeg}`);
|
|
3029
3435
|
}
|
|
3436
|
+
if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
|
|
3437
|
+
lines.push(" camera");
|
|
3438
|
+
if (viewpoint.camera.azimuth !== null) {
|
|
3439
|
+
lines.push(` azimuth ${viewpoint.camera.azimuth}`);
|
|
3440
|
+
}
|
|
3441
|
+
if (viewpoint.camera.elevation !== null) {
|
|
3442
|
+
lines.push(` elevation ${viewpoint.camera.elevation}`);
|
|
3443
|
+
}
|
|
3444
|
+
if (viewpoint.camera.roll !== null) {
|
|
3445
|
+
lines.push(` roll ${viewpoint.camera.roll}`);
|
|
3446
|
+
}
|
|
3447
|
+
if (viewpoint.camera.distance !== null) {
|
|
3448
|
+
lines.push(` distance ${viewpoint.camera.distance}`);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3030
3451
|
const layerTokens = formatDraftLayers(viewpoint.layers);
|
|
3031
3452
|
if (layerTokens.length > 0) {
|
|
3032
3453
|
lines.push(` layers ${layerTokens.join(" ")}`);
|
|
3033
3454
|
}
|
|
3455
|
+
if (viewpoint.events.length > 0) {
|
|
3456
|
+
lines.push(` events ${viewpoint.events.join(" ")}`);
|
|
3457
|
+
}
|
|
3034
3458
|
if (viewpoint.filter) {
|
|
3035
3459
|
lines.push(" filter");
|
|
3036
3460
|
if (viewpoint.filter.query) {
|
|
@@ -3103,6 +3527,65 @@
|
|
|
3103
3527
|
}
|
|
3104
3528
|
return lines;
|
|
3105
3529
|
}
|
|
3530
|
+
function formatAtlasEvent(event) {
|
|
3531
|
+
const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
|
|
3532
|
+
if (event.label) {
|
|
3533
|
+
lines.push(` label ${quoteIfNeeded(event.label)}`);
|
|
3534
|
+
}
|
|
3535
|
+
if (event.summary) {
|
|
3536
|
+
lines.push(` summary ${quoteIfNeeded(event.summary)}`);
|
|
3537
|
+
}
|
|
3538
|
+
if (event.targetObjectId) {
|
|
3539
|
+
lines.push(` target ${event.targetObjectId}`);
|
|
3540
|
+
}
|
|
3541
|
+
if (event.participantObjectIds.length > 0) {
|
|
3542
|
+
lines.push(` participants ${event.participantObjectIds.join(" ")}`);
|
|
3543
|
+
}
|
|
3544
|
+
if (event.timing) {
|
|
3545
|
+
lines.push(` timing ${quoteIfNeeded(event.timing)}`);
|
|
3546
|
+
}
|
|
3547
|
+
if (event.visibility) {
|
|
3548
|
+
lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
|
|
3549
|
+
}
|
|
3550
|
+
if (event.epoch) {
|
|
3551
|
+
lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
|
|
3552
|
+
}
|
|
3553
|
+
if (event.referencePlane) {
|
|
3554
|
+
lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
|
|
3555
|
+
}
|
|
3556
|
+
if (event.tags.length > 0) {
|
|
3557
|
+
lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
|
|
3558
|
+
}
|
|
3559
|
+
if (event.color) {
|
|
3560
|
+
lines.push(` color ${quoteIfNeeded(event.color)}`);
|
|
3561
|
+
}
|
|
3562
|
+
if (event.hidden) {
|
|
3563
|
+
lines.push(" hidden true");
|
|
3564
|
+
}
|
|
3565
|
+
if (event.positions.length > 0) {
|
|
3566
|
+
lines.push("");
|
|
3567
|
+
lines.push(" positions");
|
|
3568
|
+
for (const pose of [...event.positions].sort(comparePoseObjectId)) {
|
|
3569
|
+
lines.push(` pose ${pose.objectId}`);
|
|
3570
|
+
for (const fieldLine of formatEventPoseFields(pose)) {
|
|
3571
|
+
lines.push(` ${fieldLine}`);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
return lines;
|
|
3576
|
+
}
|
|
3577
|
+
function formatEventPoseFields(pose) {
|
|
3578
|
+
return [
|
|
3579
|
+
...formatPlacement(pose.placement),
|
|
3580
|
+
...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
|
|
3581
|
+
...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
|
|
3582
|
+
...formatOptionalUnit("inner", pose.inner),
|
|
3583
|
+
...formatOptionalUnit("outer", pose.outer)
|
|
3584
|
+
];
|
|
3585
|
+
}
|
|
3586
|
+
function hasCameraValues(camera) {
|
|
3587
|
+
return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
|
|
3588
|
+
}
|
|
3106
3589
|
function formatValue(value) {
|
|
3107
3590
|
if (Array.isArray(value)) {
|
|
3108
3591
|
return value.map((item) => quoteIfNeeded(item)).join(" ");
|
|
@@ -3144,7 +3627,7 @@
|
|
|
3144
3627
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
3145
3628
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
3146
3629
|
}
|
|
3147
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3630
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
3148
3631
|
if (layers[key] !== void 0) {
|
|
3149
3632
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
3150
3633
|
}
|
|
@@ -3172,6 +3655,9 @@
|
|
|
3172
3655
|
function compareIdLike(left, right) {
|
|
3173
3656
|
return left.id.localeCompare(right.id);
|
|
3174
3657
|
}
|
|
3658
|
+
function comparePoseObjectId(left, right) {
|
|
3659
|
+
return left.objectId.localeCompare(right.objectId);
|
|
3660
|
+
}
|
|
3175
3661
|
function objectTypeIndex(objectType) {
|
|
3176
3662
|
switch (objectType) {
|
|
3177
3663
|
case "star":
|
|
@@ -3367,6 +3853,7 @@
|
|
|
3367
3853
|
const diagnostics = [];
|
|
3368
3854
|
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
3369
3855
|
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3856
|
+
const eventIds = new Set(document2.events.map((event) => event.id));
|
|
3370
3857
|
if (!document2.system) {
|
|
3371
3858
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3372
3859
|
}
|
|
@@ -3376,6 +3863,7 @@
|
|
|
3376
3863
|
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3377
3864
|
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3378
3865
|
["relation", document2.relations.map((relation) => relation.id)],
|
|
3866
|
+
["event", document2.events.map((event) => event.id)],
|
|
3379
3867
|
["object", document2.objects.map((object) => object.id)]
|
|
3380
3868
|
]) {
|
|
3381
3869
|
for (const id of ids) {
|
|
@@ -3391,11 +3879,14 @@
|
|
|
3391
3879
|
validateRelation(relation, objectMap, diagnostics);
|
|
3392
3880
|
}
|
|
3393
3881
|
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
3394
|
-
|
|
3882
|
+
validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
|
|
3395
3883
|
}
|
|
3396
3884
|
for (const object of document2.objects) {
|
|
3397
3885
|
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
3398
3886
|
}
|
|
3887
|
+
for (const event of document2.events) {
|
|
3888
|
+
validateEvent(event, document2.system, objectMap, diagnostics);
|
|
3889
|
+
}
|
|
3399
3890
|
return diagnostics;
|
|
3400
3891
|
}
|
|
3401
3892
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -3413,15 +3904,24 @@
|
|
|
3413
3904
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3414
3905
|
}
|
|
3415
3906
|
}
|
|
3416
|
-
function
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3907
|
+
function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
|
|
3908
|
+
const filter = viewpoint.filter;
|
|
3909
|
+
if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
|
|
3910
|
+
if (filter) {
|
|
3911
|
+
for (const groupId of filter.groupIds) {
|
|
3912
|
+
if (!groupIds.has(groupId)) {
|
|
3913
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
for (const eventId of viewpoint.events ?? []) {
|
|
3918
|
+
if (!eventIds.has(eventId)) {
|
|
3919
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
|
|
3920
|
+
}
|
|
3423
3921
|
}
|
|
3424
3922
|
}
|
|
3923
|
+
validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
|
|
3924
|
+
validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
|
|
3425
3925
|
}
|
|
3426
3926
|
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
3427
3927
|
const placement = object.placement;
|
|
@@ -3434,6 +3934,12 @@
|
|
|
3434
3934
|
}
|
|
3435
3935
|
}
|
|
3436
3936
|
}
|
|
3937
|
+
if (typeof object.epoch === "string" && !object.epoch.trim()) {
|
|
3938
|
+
diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
|
|
3939
|
+
}
|
|
3940
|
+
if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
|
|
3941
|
+
diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
|
|
3942
|
+
}
|
|
3437
3943
|
if (orbitPlacement) {
|
|
3438
3944
|
if (!objectMap.has(orbitPlacement.target)) {
|
|
3439
3945
|
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
@@ -3505,6 +4011,122 @@
|
|
|
3505
4011
|
}
|
|
3506
4012
|
}
|
|
3507
4013
|
}
|
|
4014
|
+
function validateEvent(event, system, objectMap, diagnostics) {
|
|
4015
|
+
const fieldPrefix = `event.${event.id}`;
|
|
4016
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
4017
|
+
if (!event.kind.trim()) {
|
|
4018
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
4019
|
+
}
|
|
4020
|
+
if (typeof event.epoch === "string" && !event.epoch.trim()) {
|
|
4021
|
+
diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
|
|
4024
|
+
diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
|
|
4025
|
+
}
|
|
4026
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
4027
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
4028
|
+
}
|
|
4029
|
+
if (event.targetObjectId) {
|
|
4030
|
+
referencedIds.add(event.targetObjectId);
|
|
4031
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
4032
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
4036
|
+
for (const participantId of event.participantObjectIds) {
|
|
4037
|
+
referencedIds.add(participantId);
|
|
4038
|
+
if (seenParticipants.has(participantId)) {
|
|
4039
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
4040
|
+
continue;
|
|
4041
|
+
}
|
|
4042
|
+
seenParticipants.add(participantId);
|
|
4043
|
+
if (!objectMap.has(participantId)) {
|
|
4044
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
4048
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
4049
|
+
}
|
|
4050
|
+
if (event.positions.length === 0) {
|
|
4051
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
4052
|
+
}
|
|
4053
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
4054
|
+
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`));
|
|
4055
|
+
}
|
|
4056
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
4057
|
+
for (const pose of event.positions) {
|
|
4058
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
4059
|
+
if (poseIds.has(pose.objectId)) {
|
|
4060
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
4063
|
+
poseIds.add(pose.objectId);
|
|
4064
|
+
const object = objectMap.get(pose.objectId);
|
|
4065
|
+
if (!object) {
|
|
4066
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
4067
|
+
continue;
|
|
4068
|
+
}
|
|
4069
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
4070
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
4071
|
+
}
|
|
4072
|
+
validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
4073
|
+
}
|
|
4074
|
+
const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
|
|
4075
|
+
if (event.positions.length > 0 && missingPoseIds.length > 0) {
|
|
4076
|
+
diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
4080
|
+
const placement = pose.placement;
|
|
4081
|
+
if (!placement) {
|
|
4082
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
if (placement.mode === "orbit") {
|
|
4086
|
+
if (!objectMap.has(placement.target)) {
|
|
4087
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
4088
|
+
}
|
|
4089
|
+
if (placement.distance && placement.semiMajor) {
|
|
4090
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
4091
|
+
}
|
|
4092
|
+
if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
|
|
4093
|
+
diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
|
|
4094
|
+
}
|
|
4095
|
+
if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
|
|
4096
|
+
diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
|
|
4097
|
+
}
|
|
4098
|
+
if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
|
|
4099
|
+
diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, void 0, `${fieldPrefix}.period`));
|
|
4100
|
+
}
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
if (placement.mode === "surface") {
|
|
4104
|
+
const target = objectMap.get(placement.target);
|
|
4105
|
+
if (!target) {
|
|
4106
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
4107
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
4108
|
+
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`));
|
|
4109
|
+
}
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
if (placement.mode === "at") {
|
|
4113
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
4114
|
+
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`));
|
|
4115
|
+
}
|
|
4116
|
+
const reference = placement.reference;
|
|
4117
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
4118
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4119
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
4120
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4121
|
+
} else if (reference.kind === "lagrange") {
|
|
4122
|
+
if (!objectMap.has(reference.primary)) {
|
|
4123
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4124
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
4125
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
3508
4130
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3509
4131
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3510
4132
|
if (!reference) {
|
|
@@ -3610,6 +4232,52 @@
|
|
|
3610
4232
|
return null;
|
|
3611
4233
|
}
|
|
3612
4234
|
}
|
|
4235
|
+
function validateProjection(projection, diagnostics, field, viewpointId) {
|
|
4236
|
+
if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
|
|
4237
|
+
diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
|
|
4241
|
+
if (!camera) {
|
|
4242
|
+
return;
|
|
4243
|
+
}
|
|
4244
|
+
const prefix = `viewpoint.${viewpointId}.camera`;
|
|
4245
|
+
for (const [key, value] of [
|
|
4246
|
+
["azimuth", camera.azimuth],
|
|
4247
|
+
["elevation", camera.elevation],
|
|
4248
|
+
["roll", camera.roll],
|
|
4249
|
+
["distance", camera.distance]
|
|
4250
|
+
]) {
|
|
4251
|
+
if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
|
|
4252
|
+
diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
if (camera.distance !== null && projection !== "perspective") {
|
|
4256
|
+
diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, void 0, `${prefix}.distance`));
|
|
4257
|
+
}
|
|
4258
|
+
if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
|
|
4259
|
+
diagnostics.push(warn("validate.viewpoint.camera.topdownPartial", `Camera elevation/roll on topdown viewpoint "${viewpointId}" are currently stored for future 3D use and only partially affect 2D rendering.`, void 0, prefix));
|
|
4260
|
+
}
|
|
4261
|
+
if (projection === "isometric" && camera.elevation !== null) {
|
|
4262
|
+
diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
|
|
4263
|
+
}
|
|
4264
|
+
if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
|
|
4265
|
+
diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, void 0, `${prefix}.azimuth`));
|
|
4266
|
+
}
|
|
4267
|
+
const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
|
|
4268
|
+
if (!hasAnchor) {
|
|
4269
|
+
diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
function resolveEffectiveEpoch(system, object, event, pose) {
|
|
4273
|
+
return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
|
|
4274
|
+
}
|
|
4275
|
+
function resolveEffectiveReferencePlane(system, object, event, pose) {
|
|
4276
|
+
return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
|
|
4277
|
+
}
|
|
4278
|
+
function normalizeOptionalContextString(value) {
|
|
4279
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4280
|
+
}
|
|
3613
4281
|
function toleranceForField(object, field) {
|
|
3614
4282
|
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
3615
4283
|
if (typeof tolerance === "number") {
|
|
@@ -3705,6 +4373,23 @@
|
|
|
3705
4373
|
});
|
|
3706
4374
|
}
|
|
3707
4375
|
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
4376
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4377
|
+
"orbit",
|
|
4378
|
+
"distance",
|
|
4379
|
+
"semiMajor",
|
|
4380
|
+
"eccentricity",
|
|
4381
|
+
"period",
|
|
4382
|
+
"angle",
|
|
4383
|
+
"inclination",
|
|
4384
|
+
"phase",
|
|
4385
|
+
"at",
|
|
4386
|
+
"surface",
|
|
4387
|
+
"free",
|
|
4388
|
+
"inner",
|
|
4389
|
+
"outer",
|
|
4390
|
+
"epoch",
|
|
4391
|
+
"referencePlane"
|
|
4392
|
+
]);
|
|
3708
4393
|
function parseWorldOrbitAtlas(source) {
|
|
3709
4394
|
return parseAtlasSource(source);
|
|
3710
4395
|
}
|
|
@@ -3719,12 +4404,15 @@
|
|
|
3719
4404
|
const objectNodes = [];
|
|
3720
4405
|
const groups = [];
|
|
3721
4406
|
const relations = [];
|
|
4407
|
+
const events = [];
|
|
4408
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3722
4409
|
let sawDefaults = false;
|
|
3723
4410
|
let sawAtlas = false;
|
|
3724
4411
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3725
4412
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3726
4413
|
const groupIds = /* @__PURE__ */ new Set();
|
|
3727
4414
|
const relationIds = /* @__PURE__ */ new Set();
|
|
4415
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3728
4416
|
for (let index = 0; index < lines.length; index++) {
|
|
3729
4417
|
const rawLine = lines[index];
|
|
3730
4418
|
const lineNumber = index + 1;
|
|
@@ -3742,7 +4430,7 @@
|
|
|
3742
4430
|
if (!sawSchemaHeader) {
|
|
3743
4431
|
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3744
4432
|
sawSchemaHeader = true;
|
|
3745
|
-
if (prepared.comments.length > 0 && sourceSchemaVersion
|
|
4433
|
+
if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
3746
4434
|
diagnostics.push({
|
|
3747
4435
|
code: "parse.schema21.commentCompatibility",
|
|
3748
4436
|
severity: "warning",
|
|
@@ -3755,7 +4443,7 @@
|
|
|
3755
4443
|
continue;
|
|
3756
4444
|
}
|
|
3757
4445
|
if (indent === 0) {
|
|
3758
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
4446
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3759
4447
|
if (section.kind === "system") {
|
|
3760
4448
|
system = section.system;
|
|
3761
4449
|
} else if (section.kind === "defaults") {
|
|
@@ -3774,6 +4462,7 @@
|
|
|
3774
4462
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3775
4463
|
}
|
|
3776
4464
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
4465
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3777
4466
|
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3778
4467
|
const baseDocument = {
|
|
3779
4468
|
format: "worldorbit",
|
|
@@ -3781,6 +4470,7 @@
|
|
|
3781
4470
|
system,
|
|
3782
4471
|
groups,
|
|
3783
4472
|
relations,
|
|
4473
|
+
events: normalizedEvents,
|
|
3784
4474
|
objects,
|
|
3785
4475
|
diagnostics
|
|
3786
4476
|
};
|
|
@@ -3810,13 +4500,13 @@
|
|
|
3810
4500
|
return document2;
|
|
3811
4501
|
}
|
|
3812
4502
|
function assertDraftSchemaHeader(tokens, line) {
|
|
3813
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3814
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
4503
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
|
|
4504
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3815
4505
|
}
|
|
3816
4506
|
const version = tokens[1].value.toLowerCase();
|
|
3817
|
-
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
4507
|
+
return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3818
4508
|
}
|
|
3819
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
4509
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3820
4510
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
3821
4511
|
switch (keyword) {
|
|
3822
4512
|
case "system":
|
|
@@ -3834,6 +4524,8 @@
|
|
|
3834
4524
|
return {
|
|
3835
4525
|
kind: "defaults",
|
|
3836
4526
|
system,
|
|
4527
|
+
sourceSchemaVersion,
|
|
4528
|
+
diagnostics,
|
|
3837
4529
|
seenFields: /* @__PURE__ */ new Set()
|
|
3838
4530
|
};
|
|
3839
4531
|
case "atlas":
|
|
@@ -3853,7 +4545,7 @@
|
|
|
3853
4545
|
if (!system) {
|
|
3854
4546
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3855
4547
|
}
|
|
3856
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
4548
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3857
4549
|
case "annotation":
|
|
3858
4550
|
if (!system) {
|
|
3859
4551
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -3865,6 +4557,9 @@
|
|
|
3865
4557
|
case "relation":
|
|
3866
4558
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3867
4559
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
4560
|
+
case "event":
|
|
4561
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
4562
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3868
4563
|
case "object":
|
|
3869
4564
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3870
4565
|
default:
|
|
@@ -3901,7 +4596,7 @@
|
|
|
3901
4596
|
seenFields: /* @__PURE__ */ new Set()
|
|
3902
4597
|
};
|
|
3903
4598
|
}
|
|
3904
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
4599
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
3905
4600
|
if (tokens.length !== 2) {
|
|
3906
4601
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3907
4602
|
}
|
|
@@ -3918,10 +4613,12 @@
|
|
|
3918
4613
|
summary: "",
|
|
3919
4614
|
focusObjectId: null,
|
|
3920
4615
|
selectedObjectId: null,
|
|
4616
|
+
events: [],
|
|
3921
4617
|
projection: system.defaults.view,
|
|
3922
4618
|
preset: system.defaults.preset,
|
|
3923
4619
|
zoom: null,
|
|
3924
4620
|
rotationDeg: 0,
|
|
4621
|
+
camera: null,
|
|
3925
4622
|
layers: {},
|
|
3926
4623
|
filter: null
|
|
3927
4624
|
};
|
|
@@ -3930,10 +4627,15 @@
|
|
|
3930
4627
|
return {
|
|
3931
4628
|
kind: "viewpoint",
|
|
3932
4629
|
viewpoint,
|
|
4630
|
+
sourceSchemaVersion,
|
|
4631
|
+
diagnostics,
|
|
3933
4632
|
seenFields: /* @__PURE__ */ new Set(),
|
|
3934
4633
|
inFilter: false,
|
|
3935
4634
|
filterIndent: null,
|
|
3936
|
-
seenFilterFields: /* @__PURE__ */ new Set()
|
|
4635
|
+
seenFilterFields: /* @__PURE__ */ new Set(),
|
|
4636
|
+
inCamera: false,
|
|
4637
|
+
cameraIndent: null,
|
|
4638
|
+
seenCameraFields: /* @__PURE__ */ new Set()
|
|
3937
4639
|
};
|
|
3938
4640
|
}
|
|
3939
4641
|
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
@@ -4020,6 +4722,51 @@
|
|
|
4020
4722
|
seenFields: /* @__PURE__ */ new Set()
|
|
4021
4723
|
};
|
|
4022
4724
|
}
|
|
4725
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
4726
|
+
if (tokens.length !== 2) {
|
|
4727
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
4728
|
+
}
|
|
4729
|
+
const id = normalizeIdentifier2(tokens[1].value);
|
|
4730
|
+
if (!id) {
|
|
4731
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
4732
|
+
}
|
|
4733
|
+
if (eventIds.has(id)) {
|
|
4734
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
4735
|
+
}
|
|
4736
|
+
const event = {
|
|
4737
|
+
id,
|
|
4738
|
+
kind: "",
|
|
4739
|
+
label: humanizeIdentifier3(id),
|
|
4740
|
+
summary: null,
|
|
4741
|
+
targetObjectId: null,
|
|
4742
|
+
participantObjectIds: [],
|
|
4743
|
+
timing: null,
|
|
4744
|
+
visibility: null,
|
|
4745
|
+
epoch: null,
|
|
4746
|
+
referencePlane: null,
|
|
4747
|
+
tags: [],
|
|
4748
|
+
color: null,
|
|
4749
|
+
hidden: false,
|
|
4750
|
+
positions: []
|
|
4751
|
+
};
|
|
4752
|
+
const rawPoses = [];
|
|
4753
|
+
events.push(event);
|
|
4754
|
+
eventPoseNodes.set(id, rawPoses);
|
|
4755
|
+
eventIds.add(id);
|
|
4756
|
+
return {
|
|
4757
|
+
kind: "event",
|
|
4758
|
+
event,
|
|
4759
|
+
sourceSchemaVersion,
|
|
4760
|
+
diagnostics,
|
|
4761
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
4762
|
+
rawPoses,
|
|
4763
|
+
inPositions: false,
|
|
4764
|
+
positionsIndent: null,
|
|
4765
|
+
activePose: null,
|
|
4766
|
+
poseIndent: null,
|
|
4767
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4023
4770
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
4024
4771
|
if (tokens.length < 3) {
|
|
4025
4772
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -4076,6 +4823,9 @@
|
|
|
4076
4823
|
case "relation":
|
|
4077
4824
|
applyRelationField(section, tokens, line);
|
|
4078
4825
|
return;
|
|
4826
|
+
case "event":
|
|
4827
|
+
applyEventField(section, indent, tokens, line);
|
|
4828
|
+
return;
|
|
4079
4829
|
case "object":
|
|
4080
4830
|
applyObjectField(section, indent, tokens, line);
|
|
4081
4831
|
return;
|
|
@@ -4118,6 +4868,12 @@
|
|
|
4118
4868
|
const value = joinFieldValue(tokens, line);
|
|
4119
4869
|
switch (key) {
|
|
4120
4870
|
case "view":
|
|
4871
|
+
if (isSchema25Projection(value)) {
|
|
4872
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
|
|
4873
|
+
line,
|
|
4874
|
+
column: tokens[0].column
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4121
4877
|
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
4122
4878
|
return;
|
|
4123
4879
|
case "scale":
|
|
@@ -4157,14 +4913,36 @@
|
|
|
4157
4913
|
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4158
4914
|
}
|
|
4159
4915
|
function applyViewpointField2(section, indent, tokens, line) {
|
|
4916
|
+
if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
|
|
4917
|
+
section.inCamera = false;
|
|
4918
|
+
section.cameraIndent = null;
|
|
4919
|
+
}
|
|
4160
4920
|
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
4161
4921
|
section.inFilter = false;
|
|
4162
4922
|
section.filterIndent = null;
|
|
4163
4923
|
}
|
|
4924
|
+
if (section.inCamera) {
|
|
4925
|
+
applyViewpointCameraField(section, tokens, line);
|
|
4926
|
+
return;
|
|
4927
|
+
}
|
|
4164
4928
|
if (section.inFilter) {
|
|
4165
4929
|
applyViewpointFilterField(section, tokens, line);
|
|
4166
4930
|
return;
|
|
4167
4931
|
}
|
|
4932
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
|
|
4933
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4934
|
+
line,
|
|
4935
|
+
column: tokens[0].column
|
|
4936
|
+
});
|
|
4937
|
+
if (section.seenFields.has("camera")) {
|
|
4938
|
+
throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
|
|
4939
|
+
}
|
|
4940
|
+
section.seenFields.add("camera");
|
|
4941
|
+
section.inCamera = true;
|
|
4942
|
+
section.cameraIndent = indent;
|
|
4943
|
+
section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4168
4946
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
4169
4947
|
if (section.seenFields.has("filter")) {
|
|
4170
4948
|
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
@@ -4190,6 +4968,12 @@
|
|
|
4190
4968
|
section.viewpoint.selectedObjectId = value;
|
|
4191
4969
|
return;
|
|
4192
4970
|
case "projection":
|
|
4971
|
+
if (isSchema25Projection(value)) {
|
|
4972
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
|
|
4973
|
+
line,
|
|
4974
|
+
column: tokens[0].column
|
|
4975
|
+
});
|
|
4976
|
+
}
|
|
4193
4977
|
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
4194
4978
|
return;
|
|
4195
4979
|
case "preset":
|
|
@@ -4201,13 +4985,49 @@
|
|
|
4201
4985
|
case "rotation":
|
|
4202
4986
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
4203
4987
|
return;
|
|
4988
|
+
case "camera":
|
|
4989
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4990
|
+
line,
|
|
4991
|
+
column: tokens[0].column
|
|
4992
|
+
});
|
|
4993
|
+
section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
|
|
4994
|
+
return;
|
|
4204
4995
|
case "layers":
|
|
4205
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
4996
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
4997
|
+
return;
|
|
4998
|
+
case "events":
|
|
4999
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
5000
|
+
line,
|
|
5001
|
+
column: tokens[0].column
|
|
5002
|
+
});
|
|
5003
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
4206
5004
|
return;
|
|
4207
5005
|
default:
|
|
4208
5006
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4209
5007
|
}
|
|
4210
5008
|
}
|
|
5009
|
+
function applyViewpointCameraField(section, tokens, line) {
|
|
5010
|
+
const key = requireUniqueField(tokens, section.seenCameraFields, line);
|
|
5011
|
+
const value = joinFieldValue(tokens, line);
|
|
5012
|
+
const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
5013
|
+
switch (key) {
|
|
5014
|
+
case "azimuth":
|
|
5015
|
+
camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
|
|
5016
|
+
break;
|
|
5017
|
+
case "elevation":
|
|
5018
|
+
camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
|
|
5019
|
+
break;
|
|
5020
|
+
case "roll":
|
|
5021
|
+
camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
|
|
5022
|
+
break;
|
|
5023
|
+
case "distance":
|
|
5024
|
+
camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
|
|
5025
|
+
break;
|
|
5026
|
+
default:
|
|
5027
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
|
|
5028
|
+
}
|
|
5029
|
+
section.viewpoint.camera = camera;
|
|
5030
|
+
}
|
|
4211
5031
|
function applyViewpointFilterField(section, tokens, line) {
|
|
4212
5032
|
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
4213
5033
|
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
|
|
@@ -4307,6 +5127,126 @@
|
|
|
4307
5127
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4308
5128
|
}
|
|
4309
5129
|
}
|
|
5130
|
+
function applyEventField(section, indent, tokens, line) {
|
|
5131
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
5132
|
+
section.activePose = null;
|
|
5133
|
+
section.poseIndent = null;
|
|
5134
|
+
section.activePoseSeenFields.clear();
|
|
5135
|
+
}
|
|
5136
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
5137
|
+
section.inPositions = false;
|
|
5138
|
+
section.positionsIndent = null;
|
|
5139
|
+
}
|
|
5140
|
+
if (section.activePose) {
|
|
5141
|
+
if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
|
|
5142
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
|
|
5143
|
+
line,
|
|
5144
|
+
column: tokens[0]?.column ?? 1
|
|
5145
|
+
});
|
|
5146
|
+
}
|
|
5147
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
5148
|
+
return;
|
|
5149
|
+
}
|
|
5150
|
+
if (section.inPositions) {
|
|
5151
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
5152
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
5153
|
+
}
|
|
5154
|
+
const objectId = tokens[1].value;
|
|
5155
|
+
if (!objectId.trim()) {
|
|
5156
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
5157
|
+
}
|
|
5158
|
+
const rawPose = {
|
|
5159
|
+
objectId,
|
|
5160
|
+
fields: [],
|
|
5161
|
+
location: { line, column: tokens[0].column }
|
|
5162
|
+
};
|
|
5163
|
+
section.rawPoses.push(rawPose);
|
|
5164
|
+
section.activePose = rawPose;
|
|
5165
|
+
section.poseIndent = indent;
|
|
5166
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
5167
|
+
return;
|
|
5168
|
+
}
|
|
5169
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
5170
|
+
if (section.seenFields.has("positions")) {
|
|
5171
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
5172
|
+
}
|
|
5173
|
+
section.seenFields.add("positions");
|
|
5174
|
+
section.inPositions = true;
|
|
5175
|
+
section.positionsIndent = indent;
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
5179
|
+
switch (key) {
|
|
5180
|
+
case "kind":
|
|
5181
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
5182
|
+
return;
|
|
5183
|
+
case "label":
|
|
5184
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
5185
|
+
return;
|
|
5186
|
+
case "summary":
|
|
5187
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
5188
|
+
return;
|
|
5189
|
+
case "target":
|
|
5190
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
5191
|
+
return;
|
|
5192
|
+
case "participants":
|
|
5193
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
5194
|
+
return;
|
|
5195
|
+
case "timing":
|
|
5196
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
5197
|
+
return;
|
|
5198
|
+
case "visibility":
|
|
5199
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
5200
|
+
return;
|
|
5201
|
+
case "epoch":
|
|
5202
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
|
|
5203
|
+
line,
|
|
5204
|
+
column: tokens[0].column
|
|
5205
|
+
});
|
|
5206
|
+
section.event.epoch = joinFieldValue(tokens, line);
|
|
5207
|
+
return;
|
|
5208
|
+
case "referenceplane":
|
|
5209
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
|
|
5210
|
+
line,
|
|
5211
|
+
column: tokens[0].column
|
|
5212
|
+
});
|
|
5213
|
+
section.event.referencePlane = joinFieldValue(tokens, line);
|
|
5214
|
+
return;
|
|
5215
|
+
case "tags":
|
|
5216
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
5217
|
+
return;
|
|
5218
|
+
case "color":
|
|
5219
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
5220
|
+
return;
|
|
5221
|
+
case "hidden":
|
|
5222
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
5223
|
+
line,
|
|
5224
|
+
column: tokens[0].column
|
|
5225
|
+
});
|
|
5226
|
+
return;
|
|
5227
|
+
default:
|
|
5228
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
5232
|
+
if (tokens.length < 2) {
|
|
5233
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
5234
|
+
}
|
|
5235
|
+
const key = tokens[0].value;
|
|
5236
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
5237
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
5238
|
+
}
|
|
5239
|
+
if (seenFields.has(key)) {
|
|
5240
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
5241
|
+
}
|
|
5242
|
+
seenFields.add(key);
|
|
5243
|
+
return {
|
|
5244
|
+
type: "field",
|
|
5245
|
+
key,
|
|
5246
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
5247
|
+
location: { line, column: tokens[0].column }
|
|
5248
|
+
};
|
|
5249
|
+
}
|
|
4310
5250
|
function applyObjectField(section, indent, tokens, line) {
|
|
4311
5251
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
4312
5252
|
section.activeBlock = null;
|
|
@@ -4365,7 +5305,7 @@
|
|
|
4365
5305
|
function parseObjectTypeTokens(tokens, line) {
|
|
4366
5306
|
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
5307
|
}
|
|
4368
|
-
function parseLayerTokens(tokens, line) {
|
|
5308
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
4369
5309
|
const layers = {};
|
|
4370
5310
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4371
5311
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -4375,7 +5315,13 @@
|
|
|
4375
5315
|
layers["orbits-front"] = enabled;
|
|
4376
5316
|
continue;
|
|
4377
5317
|
}
|
|
4378
|
-
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
5318
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
5319
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
5320
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
5321
|
+
line,
|
|
5322
|
+
column: tokens[0]?.column ?? 1
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
4379
5325
|
layers[raw] = enabled;
|
|
4380
5326
|
}
|
|
4381
5327
|
}
|
|
@@ -4393,11 +5339,15 @@
|
|
|
4393
5339
|
}
|
|
4394
5340
|
function parseProjectionValue(value, line, column) {
|
|
4395
5341
|
const normalized = value.toLowerCase();
|
|
4396
|
-
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
5342
|
+
if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
|
|
4397
5343
|
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
4398
5344
|
}
|
|
4399
5345
|
return normalized;
|
|
4400
5346
|
}
|
|
5347
|
+
function isSchema25Projection(value) {
|
|
5348
|
+
const normalized = value.toLowerCase();
|
|
5349
|
+
return normalized === "orthographic" || normalized === "perspective";
|
|
5350
|
+
}
|
|
4401
5351
|
function parsePresetValue(value, line, column) {
|
|
4402
5352
|
const normalized = value.toLowerCase();
|
|
4403
5353
|
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
@@ -4427,6 +5377,48 @@
|
|
|
4427
5377
|
groupIds: []
|
|
4428
5378
|
};
|
|
4429
5379
|
}
|
|
5380
|
+
function createEmptyViewCamera2() {
|
|
5381
|
+
return {
|
|
5382
|
+
azimuth: null,
|
|
5383
|
+
elevation: null,
|
|
5384
|
+
roll: null,
|
|
5385
|
+
distance: null
|
|
5386
|
+
};
|
|
5387
|
+
}
|
|
5388
|
+
function parseInlineViewCamera(tokens, line, current) {
|
|
5389
|
+
if (tokens.length === 0 || tokens.length % 2 !== 0) {
|
|
5390
|
+
throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
|
|
5391
|
+
}
|
|
5392
|
+
const camera = current ? { ...current } : createEmptyViewCamera2();
|
|
5393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5394
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
5395
|
+
const fieldToken = tokens[index];
|
|
5396
|
+
const valueToken = tokens[index + 1];
|
|
5397
|
+
const key = fieldToken.value.toLowerCase();
|
|
5398
|
+
if (seen.has(key)) {
|
|
5399
|
+
throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
5400
|
+
}
|
|
5401
|
+
seen.add(key);
|
|
5402
|
+
const value = valueToken.value;
|
|
5403
|
+
switch (key) {
|
|
5404
|
+
case "azimuth":
|
|
5405
|
+
camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
|
|
5406
|
+
break;
|
|
5407
|
+
case "elevation":
|
|
5408
|
+
camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
|
|
5409
|
+
break;
|
|
5410
|
+
case "roll":
|
|
5411
|
+
camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
|
|
5412
|
+
break;
|
|
5413
|
+
case "distance":
|
|
5414
|
+
camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
|
|
5415
|
+
break;
|
|
5416
|
+
default:
|
|
5417
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
return camera;
|
|
5421
|
+
}
|
|
4430
5422
|
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
4431
5423
|
const fields = [];
|
|
4432
5424
|
let index = 0;
|
|
@@ -4514,7 +5506,7 @@
|
|
|
4514
5506
|
}
|
|
4515
5507
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4516
5508
|
const fieldMap = collectDraftFields(node.fields);
|
|
4517
|
-
const placement =
|
|
5509
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4518
5510
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4519
5511
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4520
5512
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -4559,21 +5551,41 @@
|
|
|
4559
5551
|
object.tolerances = tolerances;
|
|
4560
5552
|
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
4561
5553
|
object.typedBlocks = typedBlocks;
|
|
4562
|
-
if (sourceSchemaVersion
|
|
5554
|
+
if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4563
5555
|
if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks) {
|
|
4564
5556
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
4565
5557
|
}
|
|
4566
5558
|
}
|
|
4567
5559
|
return object;
|
|
4568
5560
|
}
|
|
4569
|
-
function
|
|
5561
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
5562
|
+
return {
|
|
5563
|
+
...event,
|
|
5564
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
5565
|
+
tags: [...new Set(event.tags)],
|
|
5566
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
function normalizeDraftEventPose(rawPose) {
|
|
5570
|
+
const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
|
|
5571
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
5572
|
+
return {
|
|
5573
|
+
objectId: rawPose.objectId,
|
|
5574
|
+
placement,
|
|
5575
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
5576
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
5577
|
+
epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
|
|
5578
|
+
referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
function collectDraftFields(fields, _mode = "object") {
|
|
4570
5582
|
const grouped = /* @__PURE__ */ new Map();
|
|
4571
5583
|
for (const field of fields) {
|
|
4572
5584
|
const spec = getDraftObjectFieldSpec(field.key);
|
|
4573
|
-
if (!spec) {
|
|
5585
|
+
if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
|
|
4574
5586
|
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4575
5587
|
}
|
|
4576
|
-
if (!spec
|
|
5588
|
+
if (!spec?.allowRepeat && grouped.has(field.key)) {
|
|
4577
5589
|
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
4578
5590
|
}
|
|
4579
5591
|
const existing = grouped.get(field.key) ?? [];
|
|
@@ -4582,7 +5594,7 @@
|
|
|
4582
5594
|
}
|
|
4583
5595
|
return grouped;
|
|
4584
5596
|
}
|
|
4585
|
-
function
|
|
5597
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
4586
5598
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4587
5599
|
const atField = fieldMap.get("at")?.[0];
|
|
4588
5600
|
const surfaceField = fieldMap.get("surface")?.[0];
|
|
@@ -4750,7 +5762,7 @@
|
|
|
4750
5762
|
}
|
|
4751
5763
|
}
|
|
4752
5764
|
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4753
|
-
if (sourceSchemaVersion
|
|
5765
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4754
5766
|
return;
|
|
4755
5767
|
}
|
|
4756
5768
|
diagnostics.push({
|
|
@@ -4762,6 +5774,34 @@
|
|
|
4762
5774
|
column: location.column
|
|
4763
5775
|
});
|
|
4764
5776
|
}
|
|
5777
|
+
function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
5778
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
diagnostics.push({
|
|
5782
|
+
code: "parse.schema25.featureCompatibility",
|
|
5783
|
+
severity: "warning",
|
|
5784
|
+
source: "parse",
|
|
5785
|
+
message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
5786
|
+
line: location.line,
|
|
5787
|
+
column: location.column
|
|
5788
|
+
});
|
|
5789
|
+
}
|
|
5790
|
+
function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
|
|
5791
|
+
return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
|
|
5792
|
+
}
|
|
5793
|
+
function schemaVersionRank(version) {
|
|
5794
|
+
switch (version) {
|
|
5795
|
+
case "2.0-draft":
|
|
5796
|
+
return 0;
|
|
5797
|
+
case "2.0":
|
|
5798
|
+
return 1;
|
|
5799
|
+
case "2.1":
|
|
5800
|
+
return 2;
|
|
5801
|
+
case "2.5":
|
|
5802
|
+
return 3;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
4765
5805
|
function preprocessAtlasSource(source) {
|
|
4766
5806
|
const chars = [...source];
|
|
4767
5807
|
const comments = [];
|
|
@@ -4849,7 +5889,7 @@
|
|
|
4849
5889
|
}
|
|
4850
5890
|
|
|
4851
5891
|
// packages/core/dist/atlas-edit.js
|
|
4852
|
-
function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.
|
|
5892
|
+
function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
|
|
4853
5893
|
return {
|
|
4854
5894
|
format: "worldorbit",
|
|
4855
5895
|
version,
|
|
@@ -4875,6 +5915,7 @@
|
|
|
4875
5915
|
},
|
|
4876
5916
|
groups: [],
|
|
4877
5917
|
relations: [],
|
|
5918
|
+
events: [],
|
|
4878
5919
|
objects: [],
|
|
4879
5920
|
diagnostics: []
|
|
4880
5921
|
};
|
|
@@ -4892,6 +5933,10 @@
|
|
|
4892
5933
|
return path.key ? document2.system?.atlasMetadata[path.key] ?? null : null;
|
|
4893
5934
|
case "group":
|
|
4894
5935
|
return path.id ? findGroup(document2, path.id) : null;
|
|
5936
|
+
case "event":
|
|
5937
|
+
return path.id ? findEvent(document2, path.id) : null;
|
|
5938
|
+
case "event-pose":
|
|
5939
|
+
return path.id && path.key ? findEventPose(document2, path.id, path.key) : null;
|
|
4895
5940
|
case "object":
|
|
4896
5941
|
return path.id ? findObject(document2, path.id) : null;
|
|
4897
5942
|
case "viewpoint":
|
|
@@ -4921,6 +5966,19 @@
|
|
|
4921
5966
|
next.groups = next.groups.filter((group) => group.id !== path.id);
|
|
4922
5967
|
}
|
|
4923
5968
|
return next;
|
|
5969
|
+
case "event":
|
|
5970
|
+
if (path.id) {
|
|
5971
|
+
next.events = next.events.filter((event) => event.id !== path.id);
|
|
5972
|
+
}
|
|
5973
|
+
return next;
|
|
5974
|
+
case "event-pose":
|
|
5975
|
+
if (path.id && path.key) {
|
|
5976
|
+
const event = findEvent(next, path.id);
|
|
5977
|
+
if (event) {
|
|
5978
|
+
event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
return next;
|
|
4924
5982
|
case "viewpoint":
|
|
4925
5983
|
if (path.id) {
|
|
4926
5984
|
system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
|
|
@@ -4989,6 +6047,22 @@
|
|
|
4989
6047
|
};
|
|
4990
6048
|
}
|
|
4991
6049
|
}
|
|
6050
|
+
if (diagnostic.field?.startsWith("event.")) {
|
|
6051
|
+
const parts = diagnostic.field.split(".");
|
|
6052
|
+
if (parts[1] && findEvent(document2, parts[1])) {
|
|
6053
|
+
if (parts[2] === "pose" && parts[3] && findEventPose(document2, parts[1], parts[3])) {
|
|
6054
|
+
return {
|
|
6055
|
+
kind: "event-pose",
|
|
6056
|
+
id: parts[1],
|
|
6057
|
+
key: parts[3]
|
|
6058
|
+
};
|
|
6059
|
+
}
|
|
6060
|
+
return {
|
|
6061
|
+
kind: "event",
|
|
6062
|
+
id: parts[1]
|
|
6063
|
+
};
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
4992
6066
|
if (diagnostic.field && diagnostic.field in ensureSystem(document2).atlasMetadata) {
|
|
4993
6067
|
return {
|
|
4994
6068
|
kind: "metadata",
|
|
@@ -5020,6 +6094,12 @@
|
|
|
5020
6094
|
function findRelation(document2, relationId) {
|
|
5021
6095
|
return document2.relations.find((relation) => relation.id === relationId) ?? null;
|
|
5022
6096
|
}
|
|
6097
|
+
function findEvent(document2, eventId) {
|
|
6098
|
+
return document2.events.find((event) => event.id === eventId) ?? null;
|
|
6099
|
+
}
|
|
6100
|
+
function findEventPose(document2, eventId, objectId) {
|
|
6101
|
+
return findEvent(document2, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
|
|
6102
|
+
}
|
|
5023
6103
|
function findViewpoint(system, viewpointId) {
|
|
5024
6104
|
return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
|
|
5025
6105
|
}
|
|
@@ -5028,8 +6108,9 @@
|
|
|
5028
6108
|
}
|
|
5029
6109
|
|
|
5030
6110
|
// packages/core/dist/load.js
|
|
5031
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
6111
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
|
|
5032
6112
|
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
6113
|
+
var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
|
|
5033
6114
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
5034
6115
|
function detectWorldOrbitSchemaVersion(source) {
|
|
5035
6116
|
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
@@ -5043,6 +6124,9 @@
|
|
|
5043
6124
|
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
5044
6125
|
return "2.1";
|
|
5045
6126
|
}
|
|
6127
|
+
if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
|
|
6128
|
+
return "2.5";
|
|
6129
|
+
}
|
|
5046
6130
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
5047
6131
|
return "2.0";
|
|
5048
6132
|
}
|
|
@@ -5103,7 +6187,7 @@
|
|
|
5103
6187
|
}
|
|
5104
6188
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
5105
6189
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
5106
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
6190
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
|
|
5107
6191
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
5108
6192
|
}
|
|
5109
6193
|
let ast;
|
|
@@ -5197,6 +6281,7 @@
|
|
|
5197
6281
|
background: true,
|
|
5198
6282
|
guides: true,
|
|
5199
6283
|
relations: true,
|
|
6284
|
+
events: true,
|
|
5200
6285
|
orbits: true,
|
|
5201
6286
|
objects: true,
|
|
5202
6287
|
labels: true,
|
|
@@ -5343,14 +6428,17 @@
|
|
|
5343
6428
|
}
|
|
5344
6429
|
function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
|
|
5345
6430
|
return {
|
|
5346
|
-
version: "2.
|
|
6431
|
+
version: "2.5",
|
|
5347
6432
|
viewpointId,
|
|
6433
|
+
activeEventId: renderOptions.activeEventId ?? null,
|
|
5348
6434
|
viewerState: { ...viewerState },
|
|
5349
6435
|
renderOptions: {
|
|
5350
6436
|
preset: renderOptions.preset,
|
|
5351
6437
|
projection: renderOptions.projection,
|
|
6438
|
+
camera: renderOptions.camera ? { ...renderOptions.camera } : null,
|
|
5352
6439
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
5353
|
-
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
|
|
6440
|
+
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
6441
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
5354
6442
|
},
|
|
5355
6443
|
filter: normalizeViewerFilter(filter)
|
|
5356
6444
|
};
|
|
@@ -5361,8 +6449,9 @@
|
|
|
5361
6449
|
function deserializeViewerAtlasState(serialized) {
|
|
5362
6450
|
const raw = JSON.parse(decodeURIComponent(serialized));
|
|
5363
6451
|
return {
|
|
5364
|
-
version: "2.0",
|
|
6452
|
+
version: raw.version === "2.0" ? "2.0" : "2.5",
|
|
5365
6453
|
viewpointId: raw.viewpointId ?? null,
|
|
6454
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
|
|
5366
6455
|
viewerState: {
|
|
5367
6456
|
scale: raw.viewerState?.scale ?? 1,
|
|
5368
6457
|
rotationDeg: raw.viewerState?.rotationDeg ?? 0,
|
|
@@ -5373,8 +6462,10 @@
|
|
|
5373
6462
|
renderOptions: {
|
|
5374
6463
|
preset: raw.renderOptions?.preset,
|
|
5375
6464
|
projection: raw.renderOptions?.projection,
|
|
6465
|
+
camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
|
|
5376
6466
|
layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
|
|
5377
|
-
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
|
|
6467
|
+
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
|
|
6468
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
|
|
5378
6469
|
},
|
|
5379
6470
|
filter: normalizeViewerFilter(raw.filter ?? null)
|
|
5380
6471
|
};
|
|
@@ -5389,8 +6480,10 @@
|
|
|
5389
6480
|
viewerState: { ...atlasState.viewerState },
|
|
5390
6481
|
renderOptions: {
|
|
5391
6482
|
...atlasState.renderOptions,
|
|
6483
|
+
camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
|
|
5392
6484
|
layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
|
|
5393
|
-
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
|
|
6485
|
+
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
|
|
6486
|
+
activeEventId: atlasState.renderOptions.activeEventId ?? null
|
|
5394
6487
|
},
|
|
5395
6488
|
filter: atlasState.filter ? { ...atlasState.filter } : null
|
|
5396
6489
|
}
|
|
@@ -5408,6 +6501,7 @@
|
|
|
5408
6501
|
background: viewpoint.layers.background,
|
|
5409
6502
|
guides: viewpoint.layers.guides,
|
|
5410
6503
|
relations: viewpoint.layers.relations,
|
|
6504
|
+
events: viewpoint.layers.events,
|
|
5411
6505
|
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
6506
|
objects: viewpoint.layers.objects,
|
|
5413
6507
|
labels: viewpoint.layers.labels,
|
|
@@ -5694,6 +6788,7 @@
|
|
|
5694
6788
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
5695
6789
|
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
6790
|
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("") : "";
|
|
6791
|
+
const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
|
|
5697
6792
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
5698
6793
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
5699
6794
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -5729,6 +6824,9 @@
|
|
|
5729
6824
|
.wo-orbit-front { opacity: 0.9; }
|
|
5730
6825
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
5731
6826
|
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
6827
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
6828
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
6829
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
5732
6830
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
5733
6831
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
5734
6832
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -5763,6 +6861,7 @@
|
|
|
5763
6861
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
5764
6862
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
5765
6863
|
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
6864
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
5766
6865
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
5767
6866
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
5768
6867
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -5770,6 +6869,20 @@
|
|
|
5770
6869
|
</g>
|
|
5771
6870
|
</g>
|
|
5772
6871
|
</svg>`;
|
|
6872
|
+
}
|
|
6873
|
+
function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
6874
|
+
const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
|
|
6875
|
+
if (participants.length === 0) {
|
|
6876
|
+
return "";
|
|
6877
|
+
}
|
|
6878
|
+
const stroke = event.event.color || theme.accent;
|
|
6879
|
+
const label = event.event.label || event.event.id;
|
|
6880
|
+
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("");
|
|
6881
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
6882
|
+
${lineMarkup}
|
|
6883
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
6884
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
6885
|
+
</g>`;
|
|
5773
6886
|
}
|
|
5774
6887
|
function renderOrbitLayer(scene, visibleObjectIds, includeStructures) {
|
|
5775
6888
|
const backParts = [];
|
|
@@ -6352,6 +7465,13 @@
|
|
|
6352
7465
|
value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
|
|
6353
7466
|
});
|
|
6354
7467
|
}
|
|
7468
|
+
if (details.relatedEvents.length > 0) {
|
|
7469
|
+
fields.set("events", {
|
|
7470
|
+
key: "events",
|
|
7471
|
+
label: "Events",
|
|
7472
|
+
value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
|
|
7473
|
+
});
|
|
7474
|
+
}
|
|
6355
7475
|
if (placement?.mode === "at") {
|
|
6356
7476
|
fields.set("placement", {
|
|
6357
7477
|
key: "placement",
|
|
@@ -6456,6 +7576,7 @@
|
|
|
6456
7576
|
padding: options.padding,
|
|
6457
7577
|
preset: options.preset,
|
|
6458
7578
|
projection: options.projection,
|
|
7579
|
+
camera: options.camera ? { ...options.camera } : null,
|
|
6459
7580
|
scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
|
|
6460
7581
|
theme: options.theme,
|
|
6461
7582
|
layers: options.layers,
|
|
@@ -6744,6 +7865,11 @@
|
|
|
6744
7865
|
if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
|
|
6745
7866
|
nextRenderOptions.projection = viewpoint.projection;
|
|
6746
7867
|
}
|
|
7868
|
+
if (viewpoint.camera) {
|
|
7869
|
+
nextRenderOptions.camera = { ...viewpoint.camera };
|
|
7870
|
+
} else if (renderOptions.camera) {
|
|
7871
|
+
nextRenderOptions.camera = null;
|
|
7872
|
+
}
|
|
6747
7873
|
if (viewpointLayers) {
|
|
6748
7874
|
nextRenderOptions.layers = viewpointLayers;
|
|
6749
7875
|
}
|
|
@@ -6766,6 +7892,12 @@
|
|
|
6766
7892
|
emitAtlasStateChange();
|
|
6767
7893
|
return true;
|
|
6768
7894
|
},
|
|
7895
|
+
getActiveEventId() {
|
|
7896
|
+
return renderOptions.activeEventId ?? null;
|
|
7897
|
+
},
|
|
7898
|
+
setActiveEvent(id) {
|
|
7899
|
+
api.setRenderOptions({ activeEventId: id });
|
|
7900
|
+
},
|
|
6769
7901
|
search(query, limit = 12) {
|
|
6770
7902
|
return searchSceneObjects(scene, query, limit);
|
|
6771
7903
|
},
|
|
@@ -7030,6 +8162,7 @@
|
|
|
7030
8162
|
orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
|
|
7031
8163
|
relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
|
|
7032
8164
|
relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
|
|
8165
|
+
relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
|
|
7033
8166
|
parent: getObjectById(renderObject.parentId),
|
|
7034
8167
|
children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
|
|
7035
8168
|
ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
|
|
@@ -7343,16 +8476,19 @@
|
|
|
7343
8476
|
function cloneRenderOptions(renderOptions) {
|
|
7344
8477
|
return {
|
|
7345
8478
|
...renderOptions,
|
|
8479
|
+
camera: renderOptions.camera ? { ...renderOptions.camera } : null,
|
|
7346
8480
|
filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
|
|
7347
8481
|
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
7348
8482
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
7349
|
-
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
|
|
8483
|
+
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
|
|
8484
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
7350
8485
|
};
|
|
7351
8486
|
}
|
|
7352
8487
|
function mergeRenderOptions(current, next) {
|
|
7353
8488
|
return {
|
|
7354
8489
|
...current,
|
|
7355
8490
|
...next,
|
|
8491
|
+
camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
|
|
7356
8492
|
filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
|
|
7357
8493
|
scaleModel: next.scaleModel ? {
|
|
7358
8494
|
...current.scaleModel ?? {},
|
|
@@ -7366,7 +8502,7 @@
|
|
|
7366
8502
|
};
|
|
7367
8503
|
}
|
|
7368
8504
|
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;
|
|
8505
|
+
return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.camera !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
|
|
7370
8506
|
}
|
|
7371
8507
|
function resolveSourceRenderOptions(loaded, renderOptions) {
|
|
7372
8508
|
const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
|
|
@@ -7677,7 +8813,11 @@
|
|
|
7677
8813
|
var FIELD_HELP = {
|
|
7678
8814
|
"defaults-view": {
|
|
7679
8815
|
description: "Sets the default camera projection for the atlas.",
|
|
7680
|
-
references: [
|
|
8816
|
+
references: [
|
|
8817
|
+
"Topdown = map-like",
|
|
8818
|
+
"Isometric = angled overview",
|
|
8819
|
+
"Orthographic/Perspective = 3D-ready semantic views"
|
|
8820
|
+
]
|
|
7681
8821
|
},
|
|
7682
8822
|
"defaults-scale": {
|
|
7683
8823
|
description: "Chooses the overall spacing/style preset used by the renderer.",
|
|
@@ -7689,15 +8829,71 @@
|
|
|
7689
8829
|
},
|
|
7690
8830
|
"viewpoint-projection": {
|
|
7691
8831
|
description: "Overrides the projection for this saved viewpoint.",
|
|
7692
|
-
references: [
|
|
8832
|
+
references: [
|
|
8833
|
+
"Topdown = flat orbital map",
|
|
8834
|
+
"Isometric = angled scene",
|
|
8835
|
+
"Orthographic/Perspective = stored with current 2D fallback"
|
|
8836
|
+
]
|
|
7693
8837
|
},
|
|
7694
8838
|
"viewpoint-zoom": {
|
|
7695
8839
|
description: "Controls how closely this viewpoint frames the system.",
|
|
7696
8840
|
references: ["1 = scene fit", "2+ = close-up"]
|
|
7697
8841
|
},
|
|
7698
8842
|
"viewpoint-rotation": {
|
|
7699
|
-
description: "
|
|
7700
|
-
references: ["90deg = quarter turn", "
|
|
8843
|
+
description: "Legacy 2D screen rotation. This is separate from the Schema 2.5 camera block.",
|
|
8844
|
+
references: ["90deg = quarter turn", "Use camera.azimuth for semantic view direction"]
|
|
8845
|
+
},
|
|
8846
|
+
"viewpoint-camera-azimuth": {
|
|
8847
|
+
description: "Horizontal camera direction in degrees for Schema 2.5 viewpoints.",
|
|
8848
|
+
references: ["0 = forward/default", "90 = quarter orbit around the scene"]
|
|
8849
|
+
},
|
|
8850
|
+
"viewpoint-camera-elevation": {
|
|
8851
|
+
description: "Vertical camera tilt in degrees for 3D-ready viewpoints.",
|
|
8852
|
+
references: ["0 = level", "30 = gentle look down"]
|
|
8853
|
+
},
|
|
8854
|
+
"viewpoint-camera-roll": {
|
|
8855
|
+
description: "Rolls the camera around its forward axis.",
|
|
8856
|
+
references: ["0 = upright", "15 = slight bank"]
|
|
8857
|
+
},
|
|
8858
|
+
"viewpoint-camera-distance": {
|
|
8859
|
+
description: "Semantic camera distance for perspective viewpoints.",
|
|
8860
|
+
references: ["4 = close", "12 = wide framing"]
|
|
8861
|
+
},
|
|
8862
|
+
"viewpoint-events": {
|
|
8863
|
+
description: "Lists event IDs that this viewpoint should feature in its detail panel.",
|
|
8864
|
+
references: ["solar-eclipse-naar", "transit-window conjunction"]
|
|
8865
|
+
},
|
|
8866
|
+
"event-kind": {
|
|
8867
|
+
description: "Short semantic event type for tooling and viewer overlays.",
|
|
8868
|
+
references: ["solar-eclipse", "lunar-eclipse", "transit"]
|
|
8869
|
+
},
|
|
8870
|
+
"event-target": {
|
|
8871
|
+
description: "Primary object this event is centered on.",
|
|
8872
|
+
references: ["Naar", "Seyra"]
|
|
8873
|
+
},
|
|
8874
|
+
"event-participants": {
|
|
8875
|
+
description: "Objects that participate in the event snapshot or description.",
|
|
8876
|
+
references: ["Iyath Naar Seyra", "Naar Seyra Orun"]
|
|
8877
|
+
},
|
|
8878
|
+
"event-timing": {
|
|
8879
|
+
description: "Free-text timing note for the event.",
|
|
8880
|
+
references: ['"Every late bloom season"', '"At local midyear"']
|
|
8881
|
+
},
|
|
8882
|
+
"event-visibility": {
|
|
8883
|
+
description: "Notes where or how the event is visible.",
|
|
8884
|
+
references: ['"Visible from Naar"', '"Southern hemisphere only"']
|
|
8885
|
+
},
|
|
8886
|
+
"event-epoch": {
|
|
8887
|
+
description: "Optional event-wide epoch that event poses inherit unless they override it.",
|
|
8888
|
+
references: ['"JY-0001.0"', '"Naar bloom cycle year 18"']
|
|
8889
|
+
},
|
|
8890
|
+
"event-referencePlane": {
|
|
8891
|
+
description: "Optional event-wide reference plane for all poses in this snapshot.",
|
|
8892
|
+
references: ["ecliptic", "naar-equatorial"]
|
|
8893
|
+
},
|
|
8894
|
+
"event-viewpoints": {
|
|
8895
|
+
description: "Viewpoint IDs that should list this event prominently.",
|
|
8896
|
+
references: ["naar-system", "overview inner-system"]
|
|
7701
8897
|
},
|
|
7702
8898
|
"placement-target": {
|
|
7703
8899
|
description: "Names the body or reference this object is attached to.",
|
|
@@ -7735,6 +8931,14 @@
|
|
|
7735
8931
|
description: "Starting position of the object along its orbit.",
|
|
7736
8932
|
references: ["0deg = start position", "180deg = opposite side"]
|
|
7737
8933
|
},
|
|
8934
|
+
"pose-epoch": {
|
|
8935
|
+
description: "Overrides the effective epoch for this pose only.",
|
|
8936
|
+
references: ['"JY-0001.0"', "Falls back to event, object, then system"]
|
|
8937
|
+
},
|
|
8938
|
+
"pose-referencePlane": {
|
|
8939
|
+
description: "Overrides the effective reference plane for this pose only.",
|
|
8940
|
+
references: ["naar-equatorial", "Falls back to event, object, then system"]
|
|
8941
|
+
},
|
|
7738
8942
|
"prop-radius": {
|
|
7739
8943
|
description: "Visual body size or real-world-inspired radius value.",
|
|
7740
8944
|
references: ["1re = Earth radius", "1sol = Sun radius"]
|
|
@@ -7817,12 +9021,23 @@
|
|
|
7817
9021
|
minimap: true,
|
|
7818
9022
|
tooltipMode: "hover",
|
|
7819
9023
|
onSelectionChange(selectedObject) {
|
|
9024
|
+
const activeEventId = selection ? selectionEventId(selection) : null;
|
|
7820
9025
|
if (ignoreViewerSelection || !selectedObject) {
|
|
7821
|
-
if (!ignoreViewerSelection
|
|
7822
|
-
|
|
9026
|
+
if (!ignoreViewerSelection) {
|
|
9027
|
+
if (selection?.kind === "event-pose" && selection.id) {
|
|
9028
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
9029
|
+
} else if (selection?.kind === "object") {
|
|
9030
|
+
setSelection(activeEventId ? { kind: "event", id: activeEventId } : null, false, true);
|
|
9031
|
+
} else if (selection?.kind === "event" && selection.id) {
|
|
9032
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
9033
|
+
}
|
|
7823
9034
|
}
|
|
7824
9035
|
return;
|
|
7825
9036
|
}
|
|
9037
|
+
if (activeEventId && findEventPose2(atlasDocument, activeEventId, selectedObject.objectId)) {
|
|
9038
|
+
setSelection({ kind: "event-pose", id: activeEventId, key: selectedObject.objectId }, false, true);
|
|
9039
|
+
return;
|
|
9040
|
+
}
|
|
7826
9041
|
setSelection({ kind: "object", id: selectedObject.objectId }, false, true);
|
|
7827
9042
|
},
|
|
7828
9043
|
onViewChange() {
|
|
@@ -7832,6 +9047,7 @@
|
|
|
7832
9047
|
toolbar.addEventListener("click", handleToolbarClick);
|
|
7833
9048
|
outline.addEventListener("click", handleOutlineClick);
|
|
7834
9049
|
overlay.addEventListener("pointerdown", handleOverlayPointerDown);
|
|
9050
|
+
inspector?.addEventListener("click", handleInspectorClick);
|
|
7835
9051
|
inspector?.addEventListener("input", handleInspectorInput);
|
|
7836
9052
|
inspector?.addEventListener("change", handleInspectorChange);
|
|
7837
9053
|
sourcePane?.addEventListener("input", handleSourceInput);
|
|
@@ -7906,6 +9122,30 @@
|
|
|
7906
9122
|
replaceAtlasDocument(nextDocument, true, { kind: "object", id });
|
|
7907
9123
|
return id;
|
|
7908
9124
|
},
|
|
9125
|
+
addEvent() {
|
|
9126
|
+
const id = createUniqueId("event", atlasDocument.events.map((event) => event.id));
|
|
9127
|
+
const created = {
|
|
9128
|
+
id,
|
|
9129
|
+
kind: "",
|
|
9130
|
+
label: humanizeIdentifier4(id),
|
|
9131
|
+
summary: null,
|
|
9132
|
+
targetObjectId: null,
|
|
9133
|
+
participantObjectIds: [],
|
|
9134
|
+
timing: null,
|
|
9135
|
+
visibility: null,
|
|
9136
|
+
epoch: null,
|
|
9137
|
+
referencePlane: null,
|
|
9138
|
+
tags: [],
|
|
9139
|
+
color: null,
|
|
9140
|
+
hidden: false,
|
|
9141
|
+
positions: []
|
|
9142
|
+
};
|
|
9143
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
9144
|
+
nextDocument.events.push(created);
|
|
9145
|
+
nextDocument.events.sort(compareEvents);
|
|
9146
|
+
replaceAtlasDocument(nextDocument, true, { kind: "event", id });
|
|
9147
|
+
return id;
|
|
9148
|
+
},
|
|
7909
9149
|
addViewpoint() {
|
|
7910
9150
|
const id = createUniqueId("viewpoint", atlasDocument.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []);
|
|
7911
9151
|
const created = {
|
|
@@ -7914,10 +9154,12 @@
|
|
|
7914
9154
|
summary: "",
|
|
7915
9155
|
focusObjectId: null,
|
|
7916
9156
|
selectedObjectId: null,
|
|
9157
|
+
events: [],
|
|
7917
9158
|
projection: atlasDocument.system?.defaults.view ?? "topdown",
|
|
7918
9159
|
preset: atlasDocument.system?.defaults.preset ?? null,
|
|
7919
9160
|
zoom: null,
|
|
7920
9161
|
rotationDeg: 0,
|
|
9162
|
+
camera: null,
|
|
7921
9163
|
layers: {},
|
|
7922
9164
|
filter: null
|
|
7923
9165
|
};
|
|
@@ -7974,6 +9216,7 @@
|
|
|
7974
9216
|
toolbar.removeEventListener("click", handleToolbarClick);
|
|
7975
9217
|
outline.removeEventListener("click", handleOutlineClick);
|
|
7976
9218
|
overlay.removeEventListener("pointerdown", handleOverlayPointerDown);
|
|
9219
|
+
inspector?.removeEventListener("click", handleInspectorClick);
|
|
7977
9220
|
inspector?.removeEventListener("input", handleInspectorInput);
|
|
7978
9221
|
inspector?.removeEventListener("change", handleInspectorChange);
|
|
7979
9222
|
sourcePane?.removeEventListener("input", handleSourceInput);
|
|
@@ -8019,7 +9262,7 @@
|
|
|
8019
9262
|
}
|
|
8020
9263
|
function getCurrentSourceForExport() {
|
|
8021
9264
|
if (dragState?.changed) {
|
|
8022
|
-
return
|
|
9265
|
+
return formatAtlasSource(atlasDocument);
|
|
8023
9266
|
}
|
|
8024
9267
|
return canonicalSource;
|
|
8025
9268
|
}
|
|
@@ -8058,7 +9301,7 @@
|
|
|
8058
9301
|
}
|
|
8059
9302
|
clearSourceInputTimer();
|
|
8060
9303
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
8061
|
-
canonicalSource =
|
|
9304
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8062
9305
|
if (!preserveSourceText) {
|
|
8063
9306
|
sourceText = canonicalSource;
|
|
8064
9307
|
}
|
|
@@ -8099,10 +9342,10 @@
|
|
|
8099
9342
|
if (commitHistory) {
|
|
8100
9343
|
history.push(createHistoryEntry());
|
|
8101
9344
|
future.length = 0;
|
|
8102
|
-
sourceText =
|
|
9345
|
+
sourceText = formatAtlasSource(nextDocument);
|
|
8103
9346
|
}
|
|
8104
9347
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
8105
|
-
canonicalSource =
|
|
9348
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8106
9349
|
diagnostics = mergeDiagnostics(loadedDiagnostics, collectDocumentDiagnostics(atlasDocument));
|
|
8107
9350
|
selection = normalizeSelection(selection);
|
|
8108
9351
|
syncViewer({
|
|
@@ -8121,11 +9364,13 @@
|
|
|
8121
9364
|
const previousState = viewer.getState();
|
|
8122
9365
|
const currentRenderOptions = viewer.getRenderOptions();
|
|
8123
9366
|
const nextPreset = atlasDocument.system?.defaults.preset ?? "atlas-card";
|
|
9367
|
+
const nextActiveEventId = selection ? selectionEventId(selection) : null;
|
|
8124
9368
|
ignoreViewerSelection = true;
|
|
8125
|
-
if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document") {
|
|
9369
|
+
if (currentRenderOptions.preset !== nextPreset || currentRenderOptions.projection !== "document" || (currentRenderOptions.activeEventId ?? null) !== nextActiveEventId) {
|
|
8126
9370
|
viewer.setRenderOptions({
|
|
8127
9371
|
preset: nextPreset,
|
|
8128
|
-
projection: "document"
|
|
9372
|
+
projection: "document",
|
|
9373
|
+
activeEventId: nextActiveEventId
|
|
8129
9374
|
});
|
|
8130
9375
|
}
|
|
8131
9376
|
viewer.setDocument(materializeAtlasDocument(atlasDocument));
|
|
@@ -8134,10 +9379,12 @@
|
|
|
8134
9379
|
} else if (options2.preserveCamera !== false) {
|
|
8135
9380
|
viewer.setState({
|
|
8136
9381
|
...previousState,
|
|
8137
|
-
selectedObjectId: selection?.kind === "object" ? selection.id ?? null : null
|
|
9382
|
+
selectedObjectId: selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null
|
|
8138
9383
|
});
|
|
8139
9384
|
} else if (selection?.kind === "object" && selection.id) {
|
|
8140
9385
|
viewer.focusObject(selection.id);
|
|
9386
|
+
} else if (selection?.kind === "event-pose" && selection.key) {
|
|
9387
|
+
viewer.focusObject(selection.key);
|
|
8141
9388
|
}
|
|
8142
9389
|
ignoreViewerSelection = false;
|
|
8143
9390
|
}
|
|
@@ -8156,8 +9403,11 @@
|
|
|
8156
9403
|
selection = normalizeSelection(nextSelection);
|
|
8157
9404
|
if (syncViewerSelection) {
|
|
8158
9405
|
ignoreViewerSelection = true;
|
|
9406
|
+
viewer.setRenderOptions({ activeEventId: selection ? selectionEventId(selection) : null });
|
|
8159
9407
|
if (selection?.kind === "object" && selection.id) {
|
|
8160
9408
|
viewer.focusObject(selection.id);
|
|
9409
|
+
} else if (selection?.kind === "event-pose" && selection.key) {
|
|
9410
|
+
viewer.focusObject(selection.key);
|
|
8161
9411
|
} else if (selection?.kind === "viewpoint" && selection.id) {
|
|
8162
9412
|
viewer.goToViewpoint(selection.id);
|
|
8163
9413
|
}
|
|
@@ -8207,6 +9457,7 @@
|
|
|
8207
9457
|
${OBJECT_TYPES.map((type) => `<option value="${escapeHtml3(type)}"${type === objectType ? " selected" : ""}>${escapeHtml3(humanizeIdentifier4(type))}</option>`).join("")}
|
|
8208
9458
|
</select>
|
|
8209
9459
|
<button type="button" data-editor-action="add-object">Add object</button>
|
|
9460
|
+
<button type="button" data-editor-action="add-event">Add event</button>
|
|
8210
9461
|
<button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
|
|
8211
9462
|
<button type="button" data-editor-action="add-annotation">Add annotation</button>
|
|
8212
9463
|
<button type="button" data-editor-action="add-metadata">Add metadata</button>
|
|
@@ -8241,6 +9492,10 @@
|
|
|
8241
9492
|
<h3>Annotations</h3>
|
|
8242
9493
|
${(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
9494
|
</div>
|
|
9495
|
+
<div class="wo-editor-outline-section">
|
|
9496
|
+
<h3>Events</h3>
|
|
9497
|
+
${atlasDocument.events.length > 0 ? atlasDocument.events.map((eventEntry) => renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets)).join("") : `<p class="wo-editor-empty">No events yet.</p>`}
|
|
9498
|
+
</div>
|
|
8244
9499
|
<div class="wo-editor-outline-section">
|
|
8245
9500
|
<h3>Objects</h3>
|
|
8246
9501
|
${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 +9533,7 @@
|
|
|
8278
9533
|
selection: selection ? { path: { ...selection } } : null,
|
|
8279
9534
|
system: atlasDocument.system,
|
|
8280
9535
|
viewpoints: atlasDocument.system?.viewpoints ?? [],
|
|
9536
|
+
events: atlasDocument.events,
|
|
8281
9537
|
objects: atlasDocument.objects
|
|
8282
9538
|
};
|
|
8283
9539
|
if (!selection) {
|
|
@@ -8306,6 +9562,16 @@
|
|
|
8306
9562
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
8307
9563
|
decorateInspectorDiagnostics(selection, diagnostics);
|
|
8308
9564
|
return;
|
|
9565
|
+
case "event":
|
|
9566
|
+
inspector.innerHTML = diagnosticSummary + renderEventInspector(formState, selection.id ?? "");
|
|
9567
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
9568
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
9569
|
+
return;
|
|
9570
|
+
case "event-pose":
|
|
9571
|
+
inspector.innerHTML = diagnosticSummary + renderEventPoseInspector(formState, selection.id ?? "", selection.key ?? "");
|
|
9572
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
9573
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
9574
|
+
return;
|
|
8309
9575
|
case "annotation":
|
|
8310
9576
|
inspector.innerHTML = diagnosticSummary + renderAnnotationInspector(formState, selection.id ?? "");
|
|
8311
9577
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
@@ -8338,10 +9604,11 @@
|
|
|
8338
9604
|
return;
|
|
8339
9605
|
}
|
|
8340
9606
|
overlay.innerHTML = "";
|
|
8341
|
-
|
|
9607
|
+
const selectedObjectId = selection?.kind === "object" ? selection.id ?? null : selection?.kind === "event-pose" ? selection.key ?? null : null;
|
|
9608
|
+
if (!selectedObjectId) {
|
|
8342
9609
|
return;
|
|
8343
9610
|
}
|
|
8344
|
-
const details = viewer.getObjectDetails(
|
|
9611
|
+
const details = viewer.getObjectDetails(selectedObjectId);
|
|
8345
9612
|
if (!details) {
|
|
8346
9613
|
return;
|
|
8347
9614
|
}
|
|
@@ -8453,6 +9720,9 @@
|
|
|
8453
9720
|
case "add-viewpoint":
|
|
8454
9721
|
api.addViewpoint();
|
|
8455
9722
|
return;
|
|
9723
|
+
case "add-event":
|
|
9724
|
+
api.addEvent();
|
|
9725
|
+
return;
|
|
8456
9726
|
case "add-annotation":
|
|
8457
9727
|
api.addAnnotation();
|
|
8458
9728
|
return;
|
|
@@ -8487,6 +9757,32 @@
|
|
|
8487
9757
|
key: button.dataset.pathKey || void 0
|
|
8488
9758
|
}, true, true);
|
|
8489
9759
|
}
|
|
9760
|
+
function handleInspectorClick(event) {
|
|
9761
|
+
const pathButton = event.target?.closest("[data-path-kind]");
|
|
9762
|
+
if (pathButton) {
|
|
9763
|
+
setSelection({
|
|
9764
|
+
kind: pathButton.dataset.pathKind,
|
|
9765
|
+
id: pathButton.dataset.pathId || void 0,
|
|
9766
|
+
key: pathButton.dataset.pathKey || void 0
|
|
9767
|
+
}, true, true);
|
|
9768
|
+
return;
|
|
9769
|
+
}
|
|
9770
|
+
const actionButton = event.target?.closest("[data-editor-action]");
|
|
9771
|
+
if (!actionButton) {
|
|
9772
|
+
return;
|
|
9773
|
+
}
|
|
9774
|
+
if (actionButton.dataset.editorAction === "add-event-pose") {
|
|
9775
|
+
const eventId = actionButton.dataset.editorEventId || (selection?.kind === "event" || selection?.kind === "event-pose" ? selection.id ?? "" : "");
|
|
9776
|
+
if (!eventId) {
|
|
9777
|
+
return;
|
|
9778
|
+
}
|
|
9779
|
+
const nextDocument = addEventPose(atlasDocument, eventId);
|
|
9780
|
+
const createdEvent = nextDocument.events.find((entry) => entry.id === eventId);
|
|
9781
|
+
const createdPose = createdEvent?.positions.at(-1) ?? createdEvent?.positions[0];
|
|
9782
|
+
replaceAtlasDocument(nextDocument, true, createdPose ? { kind: "event-pose", id: eventId, key: createdPose.objectId } : { kind: "event", id: eventId });
|
|
9783
|
+
return;
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
8490
9786
|
function handleInspectorInput() {
|
|
8491
9787
|
applyInspectorState(false);
|
|
8492
9788
|
}
|
|
@@ -8510,6 +9806,12 @@
|
|
|
8510
9806
|
case "viewpoint":
|
|
8511
9807
|
replaceAtlasDocument(buildViewpointDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
8512
9808
|
return;
|
|
9809
|
+
case "event":
|
|
9810
|
+
replaceAtlasDocument(buildEventDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
9811
|
+
return;
|
|
9812
|
+
case "event-pose":
|
|
9813
|
+
replaceAtlasDocument(buildEventPoseDocumentFromInspector(selection.id ?? "", selection.key ?? ""), commitHistory, selection, false);
|
|
9814
|
+
return;
|
|
8513
9815
|
case "annotation":
|
|
8514
9816
|
replaceAtlasDocument(buildAnnotationDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
8515
9817
|
return;
|
|
@@ -8578,6 +9880,7 @@
|
|
|
8578
9880
|
kind,
|
|
8579
9881
|
objectId,
|
|
8580
9882
|
pointerId: event.pointerId,
|
|
9883
|
+
path: selection ? { ...selection } : { kind: "object", id: objectId },
|
|
8581
9884
|
startedFrom: createHistoryEntry(),
|
|
8582
9885
|
changed: false,
|
|
8583
9886
|
orbitRadiusContext: kind === "orbit-radius" && details ? createOrbitRadiusDragContext(atlasDocument, viewer.getScene(), details) : null
|
|
@@ -8586,7 +9889,7 @@
|
|
|
8586
9889
|
event.preventDefault();
|
|
8587
9890
|
}
|
|
8588
9891
|
function handleWindowPointerMove(event) {
|
|
8589
|
-
if (!dragState || dragState.pointerId !== event.pointerId || selection
|
|
9892
|
+
if (!dragState || dragState.pointerId !== event.pointerId || !selection || selectionKey(selection) !== selectionKey(dragState.path)) {
|
|
8590
9893
|
return;
|
|
8591
9894
|
}
|
|
8592
9895
|
const details = viewer.getObjectDetails(dragState.objectId);
|
|
@@ -8598,27 +9901,27 @@
|
|
|
8598
9901
|
switch (dragState.kind) {
|
|
8599
9902
|
case "orbit-phase":
|
|
8600
9903
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
8601
|
-
nextDocument = updateOrbitPhase(atlasDocument, dragState.objectId, details, pointer);
|
|
9904
|
+
nextDocument = updateOrbitPhase(atlasDocument, dragState.path, dragState.objectId, details, pointer);
|
|
8602
9905
|
}
|
|
8603
9906
|
break;
|
|
8604
9907
|
case "orbit-radius":
|
|
8605
9908
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
8606
|
-
nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
9909
|
+
nextDocument = updateOrbitRadius(atlasDocument, dragState.path, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
8607
9910
|
}
|
|
8608
9911
|
break;
|
|
8609
9912
|
case "at-reference":
|
|
8610
9913
|
if (details.object.placement?.mode === "at") {
|
|
8611
|
-
nextDocument = updateAtReference(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
9914
|
+
nextDocument = updateAtReference(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
8612
9915
|
}
|
|
8613
9916
|
break;
|
|
8614
9917
|
case "surface-target":
|
|
8615
9918
|
if (details.object.placement?.mode === "surface") {
|
|
8616
|
-
nextDocument = updateSurfaceTarget(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
9919
|
+
nextDocument = updateSurfaceTarget(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
8617
9920
|
}
|
|
8618
9921
|
break;
|
|
8619
9922
|
case "free-distance":
|
|
8620
9923
|
if (details.object.placement?.mode === "free") {
|
|
8621
|
-
nextDocument = updateFreeDistance(atlasDocument, dragState.objectId, viewer.getScene(), details, pointer);
|
|
9924
|
+
nextDocument = updateFreeDistance(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), details, pointer);
|
|
8622
9925
|
}
|
|
8623
9926
|
break;
|
|
8624
9927
|
}
|
|
@@ -8650,7 +9953,7 @@
|
|
|
8650
9953
|
}
|
|
8651
9954
|
history.push(dragState.startedFrom);
|
|
8652
9955
|
future.length = 0;
|
|
8653
|
-
canonicalSource =
|
|
9956
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
8654
9957
|
sourceText = canonicalSource;
|
|
8655
9958
|
dragState = null;
|
|
8656
9959
|
renderAll();
|
|
@@ -8748,15 +10051,18 @@
|
|
|
8748
10051
|
preset: readOptionalTextInput(form, "viewpoint-preset") ?? null,
|
|
8749
10052
|
zoom: parseNullableNumber(readOptionalTextInput(form, "viewpoint-zoom")),
|
|
8750
10053
|
rotationDeg: parseNullableNumber(readOptionalTextInput(form, "viewpoint-rotation")) ?? 0,
|
|
10054
|
+
camera: buildViewCameraFromForm(form),
|
|
8751
10055
|
layers: {
|
|
8752
10056
|
background: readCheckbox(form, "layer-background"),
|
|
8753
10057
|
guides: readCheckbox(form, "layer-guides"),
|
|
8754
10058
|
"orbits-back": readCheckbox(form, "layer-orbits-back"),
|
|
8755
10059
|
"orbits-front": readCheckbox(form, "layer-orbits-front"),
|
|
10060
|
+
events: readCheckbox(form, "layer-events"),
|
|
8756
10061
|
objects: readCheckbox(form, "layer-objects"),
|
|
8757
10062
|
labels: readCheckbox(form, "layer-labels"),
|
|
8758
10063
|
metadata: readCheckbox(form, "layer-metadata")
|
|
8759
10064
|
},
|
|
10065
|
+
events: splitTokens(readOptionalTextInput(form, "viewpoint-events")),
|
|
8760
10066
|
filter: {
|
|
8761
10067
|
query: readOptionalTextInput(form, "filter-query"),
|
|
8762
10068
|
objectTypes: parseObjectTypes(readOptionalTextInput(form, "filter-object-types")),
|
|
@@ -8770,6 +10076,70 @@
|
|
|
8770
10076
|
}
|
|
8771
10077
|
return nextDocument;
|
|
8772
10078
|
}
|
|
10079
|
+
function buildEventDocumentFromInspector(currentId) {
|
|
10080
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
10081
|
+
const form = inspector?.querySelector("form[data-editor-form='event']");
|
|
10082
|
+
const current = nextDocument.events.find((entry) => entry.id === currentId);
|
|
10083
|
+
if (!form || !current) {
|
|
10084
|
+
return nextDocument;
|
|
10085
|
+
}
|
|
10086
|
+
const nextId = readTextInput(form, "event-id") || current.id;
|
|
10087
|
+
const replacement = {
|
|
10088
|
+
...current,
|
|
10089
|
+
id: nextId,
|
|
10090
|
+
kind: readTextInput(form, "event-kind"),
|
|
10091
|
+
label: readTextInput(form, "event-label") || current.label,
|
|
10092
|
+
summary: readOptionalTextInput(form, "event-summary"),
|
|
10093
|
+
targetObjectId: readOptionalTextInput(form, "event-target"),
|
|
10094
|
+
participantObjectIds: splitTokens(readOptionalTextInput(form, "event-participants")),
|
|
10095
|
+
timing: readOptionalTextInput(form, "event-timing"),
|
|
10096
|
+
visibility: readOptionalTextInput(form, "event-visibility"),
|
|
10097
|
+
epoch: readOptionalTextInput(form, "event-epoch"),
|
|
10098
|
+
referencePlane: readOptionalTextInput(form, "event-referencePlane"),
|
|
10099
|
+
tags: splitTokens(readOptionalTextInput(form, "event-tags")),
|
|
10100
|
+
color: readOptionalTextInput(form, "event-color"),
|
|
10101
|
+
hidden: readCheckbox(form, "event-hidden")
|
|
10102
|
+
};
|
|
10103
|
+
nextDocument.events = nextDocument.events.filter((entry) => entry.id !== current.id).concat(replacement).sort(compareEvents);
|
|
10104
|
+
syncEventViewpointReferences(nextDocument, current.id, replacement.id, splitTokens(readOptionalTextInput(form, "event-viewpoints")));
|
|
10105
|
+
if (current.id !== replacement.id) {
|
|
10106
|
+
selection = { kind: "event", id: replacement.id };
|
|
10107
|
+
}
|
|
10108
|
+
return nextDocument;
|
|
10109
|
+
}
|
|
10110
|
+
function buildEventPoseDocumentFromInspector(eventId, objectId) {
|
|
10111
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
10112
|
+
const form = inspector?.querySelector("form[data-editor-form='event-pose']");
|
|
10113
|
+
const eventEntry = nextDocument.events.find((entry) => entry.id === eventId);
|
|
10114
|
+
const currentPose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
10115
|
+
if (!form || !eventEntry || !currentPose) {
|
|
10116
|
+
return nextDocument;
|
|
10117
|
+
}
|
|
10118
|
+
const nextObjectId = readTextInput(form, "pose-object-id") || currentPose.objectId;
|
|
10119
|
+
const replacement = {
|
|
10120
|
+
objectId: nextObjectId,
|
|
10121
|
+
placement: buildPlacementFromPoseForm(form, currentPose),
|
|
10122
|
+
epoch: readOptionalTextInput(form, "pose-epoch"),
|
|
10123
|
+
referencePlane: readOptionalTextInput(form, "pose-referencePlane")
|
|
10124
|
+
};
|
|
10125
|
+
const inner = parseOptionalUnit(readOptionalTextInput(form, "prop-inner"));
|
|
10126
|
+
const outer = parseOptionalUnit(readOptionalTextInput(form, "prop-outer"));
|
|
10127
|
+
if (inner) {
|
|
10128
|
+
replacement.inner = inner;
|
|
10129
|
+
}
|
|
10130
|
+
if (outer) {
|
|
10131
|
+
replacement.outer = outer;
|
|
10132
|
+
}
|
|
10133
|
+
eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== currentPose.objectId).concat(replacement).sort(compareEventPoses);
|
|
10134
|
+
if (eventEntry.targetObjectId !== replacement.objectId && !eventEntry.participantObjectIds.includes(replacement.objectId)) {
|
|
10135
|
+
eventEntry.participantObjectIds.push(replacement.objectId);
|
|
10136
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
10137
|
+
}
|
|
10138
|
+
if (currentPose.objectId !== replacement.objectId) {
|
|
10139
|
+
selection = { kind: "event-pose", id: eventId, key: replacement.objectId };
|
|
10140
|
+
}
|
|
10141
|
+
return nextDocument;
|
|
10142
|
+
}
|
|
8773
10143
|
function buildAnnotationDocumentFromInspector(currentId) {
|
|
8774
10144
|
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
8775
10145
|
const form = inspector?.querySelector("form[data-editor-form='annotation']");
|
|
@@ -8962,7 +10332,7 @@
|
|
|
8962
10332
|
const atlasDocument2 = cloneAtlasDocument(options.atlasDocument);
|
|
8963
10333
|
return {
|
|
8964
10334
|
atlasDocument: atlasDocument2,
|
|
8965
|
-
source:
|
|
10335
|
+
source: formatAtlasSource(atlasDocument2),
|
|
8966
10336
|
diagnostics: collectDocumentDiagnostics(atlasDocument2)
|
|
8967
10337
|
};
|
|
8968
10338
|
}
|
|
@@ -8972,7 +10342,7 @@
|
|
|
8972
10342
|
const atlasDocument2 = loaded.value.atlasDocument ?? upgradeDocumentToV2(loaded.value.document);
|
|
8973
10343
|
return {
|
|
8974
10344
|
atlasDocument: atlasDocument2,
|
|
8975
|
-
source:
|
|
10345
|
+
source: formatAtlasSource(atlasDocument2),
|
|
8976
10346
|
diagnostics: mergeDiagnostics(resolveAtlasDiagnostics(atlasDocument2, loaded.diagnostics), collectDocumentDiagnostics(atlasDocument2))
|
|
8977
10347
|
};
|
|
8978
10348
|
}
|
|
@@ -8980,10 +10350,13 @@
|
|
|
8980
10350
|
const atlasDocument = createEmptyAtlasDocument("WorldOrbit");
|
|
8981
10351
|
return {
|
|
8982
10352
|
atlasDocument,
|
|
8983
|
-
source:
|
|
10353
|
+
source: formatAtlasSource(atlasDocument),
|
|
8984
10354
|
diagnostics: collectDocumentDiagnostics(atlasDocument)
|
|
8985
10355
|
};
|
|
8986
10356
|
}
|
|
10357
|
+
function formatAtlasSource(document2) {
|
|
10358
|
+
return formatDocument(document2, { schema: document2.version });
|
|
10359
|
+
}
|
|
8987
10360
|
function buildEditorMarkup() {
|
|
8988
10361
|
const previewOpen = shouldPreviewSectionBeOpenByDefault();
|
|
8989
10362
|
return `<section class="wo-editor-shell">
|
|
@@ -9098,6 +10471,12 @@
|
|
|
9098
10471
|
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
10472
|
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
10473
|
}
|
|
10474
|
+
function renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets) {
|
|
10475
|
+
return `<div class="wo-editor-outline-group">
|
|
10476
|
+
${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
|
|
10477
|
+
${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>` : ""}
|
|
10478
|
+
</div>`;
|
|
10479
|
+
}
|
|
9101
10480
|
function renderSystemInspector(formState) {
|
|
9102
10481
|
return `<form class="wo-editor-form" data-editor-form="system">
|
|
9103
10482
|
<h2>System</h2>
|
|
@@ -9111,7 +10490,9 @@
|
|
|
9111
10490
|
<h2>Defaults</h2>
|
|
9112
10491
|
${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
|
|
9113
10492
|
["topdown", "Topdown"],
|
|
9114
|
-
["isometric", "Isometric"]
|
|
10493
|
+
["isometric", "Isometric"],
|
|
10494
|
+
["orthographic", "Orthographic"],
|
|
10495
|
+
["perspective", "Perspective"]
|
|
9115
10496
|
], defaults?.view ?? "topdown")}
|
|
9116
10497
|
${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
|
|
9117
10498
|
${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
|
|
@@ -9147,7 +10528,9 @@
|
|
|
9147
10528
|
${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
|
|
9148
10529
|
${renderSelectField("Projection", "viewpoint-projection", [
|
|
9149
10530
|
["topdown", "Topdown"],
|
|
9150
|
-
["isometric", "Isometric"]
|
|
10531
|
+
["isometric", "Isometric"],
|
|
10532
|
+
["orthographic", "Orthographic"],
|
|
10533
|
+
["perspective", "Perspective"]
|
|
9151
10534
|
], viewpoint.projection)}
|
|
9152
10535
|
${renderSelectField("Preset", "viewpoint-preset", [
|
|
9153
10536
|
["", "Document default"],
|
|
@@ -9158,12 +10541,18 @@
|
|
|
9158
10541
|
], viewpoint.preset ?? "")}
|
|
9159
10542
|
${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
|
|
9160
10543
|
${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
|
|
10544
|
+
${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === void 0 ? "" : String(viewpoint.camera.azimuth))}
|
|
10545
|
+
${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === void 0 ? "" : String(viewpoint.camera.elevation))}
|
|
10546
|
+
${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === void 0 ? "" : String(viewpoint.camera.roll))}
|
|
10547
|
+
${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === void 0 ? "" : String(viewpoint.camera.distance))}
|
|
10548
|
+
<p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 2.5 view direction and framing.</p>`)}
|
|
9161
10549
|
${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
|
|
9162
10550
|
<legend>Layers</legend>
|
|
9163
10551
|
${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
|
|
9164
10552
|
${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
|
|
9165
10553
|
${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
|
|
9166
10554
|
${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
|
|
10555
|
+
${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
|
|
9167
10556
|
${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
|
|
9168
10557
|
${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
|
|
9169
10558
|
${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
|
|
@@ -9171,7 +10560,75 @@
|
|
|
9171
10560
|
${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
|
|
9172
10561
|
${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
|
|
9173
10562
|
${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
|
|
9174
|
-
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
10563
|
+
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
10564
|
+
${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
|
|
10565
|
+
</form>`;
|
|
10566
|
+
}
|
|
10567
|
+
function renderEventInspector(formState, id) {
|
|
10568
|
+
const eventEntry = formState.events.find((entry) => entry.id === id);
|
|
10569
|
+
if (!eventEntry) {
|
|
10570
|
+
return `<p class="wo-editor-empty">Event not found.</p>`;
|
|
10571
|
+
}
|
|
10572
|
+
const linkedViewpoints = formState.viewpoints.filter((viewpoint) => viewpoint.events.includes(eventEntry.id)).map((viewpoint) => viewpoint.id).join(" ");
|
|
10573
|
+
return `<form class="wo-editor-form" data-editor-form="event">
|
|
10574
|
+
<h2>Event</h2>
|
|
10575
|
+
${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
|
|
10576
|
+
${renderTextField("Kind", "event-kind", eventEntry.kind)}
|
|
10577
|
+
${renderTextField("Label", "event-label", eventEntry.label)}
|
|
10578
|
+
${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
|
|
10579
|
+
${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
|
|
10580
|
+
${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
|
|
10581
|
+
${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
|
|
10582
|
+
${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
|
|
10583
|
+
${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
|
|
10584
|
+
${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
|
|
10585
|
+
${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
|
|
10586
|
+
${renderTextField("Color", "event-color", eventEntry.color ?? "")}
|
|
10587
|
+
${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
|
|
10588
|
+
${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
|
|
10589
|
+
${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>`}
|
|
10590
|
+
<div class="wo-editor-inline-actions">
|
|
10591
|
+
<button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml3(eventEntry.id)}">Add pose</button>
|
|
10592
|
+
</div>`)}
|
|
10593
|
+
</form>`;
|
|
10594
|
+
}
|
|
10595
|
+
function renderEventPoseInspector(formState, eventId, objectId) {
|
|
10596
|
+
const eventEntry = formState.events.find((entry) => entry.id === eventId);
|
|
10597
|
+
const pose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
10598
|
+
if (!eventEntry || !pose) {
|
|
10599
|
+
return `<p class="wo-editor-empty">Event pose not found.</p>`;
|
|
10600
|
+
}
|
|
10601
|
+
const placementMode = pose.placement?.mode ?? "";
|
|
10602
|
+
const placementTarget = pose.placement?.mode === "orbit" || pose.placement?.mode === "surface" || pose.placement?.mode === "at" ? pose.placement.target : "";
|
|
10603
|
+
const freeValue = pose.placement?.mode === "free" ? pose.placement.distance ? formatUnitValue3(pose.placement.distance) : pose.placement.descriptor ?? "" : "";
|
|
10604
|
+
return `<form class="wo-editor-form" data-editor-form="event-pose">
|
|
10605
|
+
<h2>Event Pose</h2>
|
|
10606
|
+
<p class="wo-editor-inline-note">Event <strong>${escapeHtml3(eventEntry.label || eventEntry.id)}</strong></p>
|
|
10607
|
+
${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
|
|
10608
|
+
<div class="wo-editor-inline-actions">
|
|
10609
|
+
<button type="button" data-path-kind="event" data-path-id="${escapeHtml3(eventEntry.id)}">Select event</button>
|
|
10610
|
+
</div>`, true)}
|
|
10611
|
+
${renderInspectorSection("event-pose", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
|
|
10612
|
+
["", "None"],
|
|
10613
|
+
["orbit", "Orbit"],
|
|
10614
|
+
["at", "At"],
|
|
10615
|
+
["surface", "Surface"],
|
|
10616
|
+
["free", "Free"]
|
|
10617
|
+
], placementMode)}
|
|
10618
|
+
${renderTextField("Placement target", "placement-target", placementTarget)}
|
|
10619
|
+
${renderTextField("Free value", "placement-free", freeValue)}
|
|
10620
|
+
${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue3(pose.placement.distance) : "")}
|
|
10621
|
+
${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue3(pose.placement.semiMajor) : "")}
|
|
10622
|
+
${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== void 0 ? String(pose.placement.eccentricity) : "")}
|
|
10623
|
+
${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue3(pose.placement.period) : "")}
|
|
10624
|
+
${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue3(pose.placement.angle) : "")}
|
|
10625
|
+
${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue3(pose.placement.inclination) : "")}
|
|
10626
|
+
${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue3(pose.placement.phase) : "")}
|
|
10627
|
+
${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue3(pose.inner) : "")}
|
|
10628
|
+
${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue3(pose.outer) : "")}`, true)}
|
|
10629
|
+
${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
|
|
10630
|
+
${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
|
|
10631
|
+
<p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
|
|
9175
10632
|
</form>`;
|
|
9176
10633
|
}
|
|
9177
10634
|
function renderAnnotationInspector(formState, id) {
|
|
@@ -9276,13 +10733,19 @@
|
|
|
9276
10733
|
return form.elements.namedItem(name)?.checked ?? false;
|
|
9277
10734
|
}
|
|
9278
10735
|
function buildPlacementFromForm(form, current) {
|
|
10736
|
+
return buildPlacementFromValues(form, current.placement, current.id);
|
|
10737
|
+
}
|
|
10738
|
+
function buildPlacementFromPoseForm(form, current) {
|
|
10739
|
+
return buildPlacementFromValues(form, current.placement, current.objectId);
|
|
10740
|
+
}
|
|
10741
|
+
function buildPlacementFromValues(form, currentPlacement, fallbackTarget) {
|
|
9279
10742
|
const mode = readTextInput(form, "placement-mode");
|
|
9280
10743
|
const target = readOptionalTextInput(form, "placement-target");
|
|
9281
10744
|
switch (mode) {
|
|
9282
10745
|
case "orbit":
|
|
9283
10746
|
return {
|
|
9284
10747
|
mode,
|
|
9285
|
-
target: target ?? (
|
|
10748
|
+
target: target ?? (currentPlacement?.mode === "orbit" ? currentPlacement.target : fallbackTarget),
|
|
9286
10749
|
distance: parseOptionalUnit(readOptionalTextInput(form, "placement-distance")),
|
|
9287
10750
|
semiMajor: parseOptionalUnit(readOptionalTextInput(form, "placement-semiMajor")),
|
|
9288
10751
|
eccentricity: parseNullableNumber(readOptionalTextInput(form, "placement-eccentricity")) ?? void 0,
|
|
@@ -9294,13 +10757,13 @@
|
|
|
9294
10757
|
case "at":
|
|
9295
10758
|
return {
|
|
9296
10759
|
mode,
|
|
9297
|
-
target: target ??
|
|
9298
|
-
reference: parseAtReferenceString(target ??
|
|
10760
|
+
target: target ?? fallbackTarget,
|
|
10761
|
+
reference: parseAtReferenceString(target ?? fallbackTarget)
|
|
9299
10762
|
};
|
|
9300
10763
|
case "surface":
|
|
9301
10764
|
return {
|
|
9302
10765
|
mode,
|
|
9303
|
-
target: target ??
|
|
10766
|
+
target: target ?? fallbackTarget
|
|
9304
10767
|
};
|
|
9305
10768
|
case "free": {
|
|
9306
10769
|
const freeValue = readOptionalTextInput(form, "placement-free");
|
|
@@ -9350,6 +10813,15 @@
|
|
|
9350
10813
|
const parsed = Number(value);
|
|
9351
10814
|
return Number.isFinite(parsed) ? parsed : null;
|
|
9352
10815
|
}
|
|
10816
|
+
function buildViewCameraFromForm(form) {
|
|
10817
|
+
const camera = {
|
|
10818
|
+
azimuth: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-azimuth")),
|
|
10819
|
+
elevation: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-elevation")),
|
|
10820
|
+
roll: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-roll")),
|
|
10821
|
+
distance: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-distance"))
|
|
10822
|
+
};
|
|
10823
|
+
return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null ? camera : null;
|
|
10824
|
+
}
|
|
9353
10825
|
function parseObjectTypes(value) {
|
|
9354
10826
|
const tokens = splitTokens(value);
|
|
9355
10827
|
return tokens.filter((token) => OBJECT_TYPES.includes(token));
|
|
@@ -9431,9 +10903,48 @@
|
|
|
9431
10903
|
annotation.sourceObjectId = toId;
|
|
9432
10904
|
}
|
|
9433
10905
|
}
|
|
10906
|
+
for (const eventEntry of document2.events) {
|
|
10907
|
+
if (eventEntry.targetObjectId === fromId) {
|
|
10908
|
+
eventEntry.targetObjectId = toId;
|
|
10909
|
+
}
|
|
10910
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.map((entry) => entry === fromId ? toId : entry);
|
|
10911
|
+
for (const pose of eventEntry.positions) {
|
|
10912
|
+
if (pose.objectId === fromId) {
|
|
10913
|
+
pose.objectId = toId;
|
|
10914
|
+
}
|
|
10915
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === fromId) {
|
|
10916
|
+
pose.placement.target = toId;
|
|
10917
|
+
}
|
|
10918
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === fromId) {
|
|
10919
|
+
pose.placement.target = toId;
|
|
10920
|
+
}
|
|
10921
|
+
if (pose.placement?.mode === "at") {
|
|
10922
|
+
const reference = pose.placement.reference;
|
|
10923
|
+
if (reference.kind === "anchor" && reference.objectId === fromId) {
|
|
10924
|
+
reference.objectId = toId;
|
|
10925
|
+
}
|
|
10926
|
+
if (reference.kind === "lagrange") {
|
|
10927
|
+
if (reference.primary === fromId) {
|
|
10928
|
+
reference.primary = toId;
|
|
10929
|
+
}
|
|
10930
|
+
if (reference.secondary === fromId) {
|
|
10931
|
+
reference.secondary = toId;
|
|
10932
|
+
}
|
|
10933
|
+
}
|
|
10934
|
+
pose.placement.target = formatAtReference2(reference);
|
|
10935
|
+
}
|
|
10936
|
+
}
|
|
10937
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
10938
|
+
}
|
|
9434
10939
|
}
|
|
9435
10940
|
function removeSelectedNode(document2, selection) {
|
|
9436
10941
|
const next = removeAtlasDocumentNode(document2, selection);
|
|
10942
|
+
if (selection.kind === "event" && selection.id) {
|
|
10943
|
+
for (const viewpoint of next.system?.viewpoints ?? []) {
|
|
10944
|
+
viewpoint.events = viewpoint.events.filter((eventId) => eventId !== selection.id);
|
|
10945
|
+
}
|
|
10946
|
+
return next;
|
|
10947
|
+
}
|
|
9437
10948
|
if (selection.kind !== "object" || !selection.id) {
|
|
9438
10949
|
return next;
|
|
9439
10950
|
}
|
|
@@ -9468,9 +10979,45 @@
|
|
|
9468
10979
|
annotation.sourceObjectId = null;
|
|
9469
10980
|
}
|
|
9470
10981
|
}
|
|
10982
|
+
for (const eventEntry of next.events) {
|
|
10983
|
+
if (eventEntry.targetObjectId === selection.id) {
|
|
10984
|
+
eventEntry.targetObjectId = null;
|
|
10985
|
+
}
|
|
10986
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.filter((entry) => entry !== selection.id);
|
|
10987
|
+
eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== selection.id);
|
|
10988
|
+
for (const pose of eventEntry.positions) {
|
|
10989
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === selection.id) {
|
|
10990
|
+
pose.placement = null;
|
|
10991
|
+
}
|
|
10992
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === selection.id) {
|
|
10993
|
+
pose.placement = null;
|
|
10994
|
+
}
|
|
10995
|
+
if (pose.placement?.mode === "at") {
|
|
10996
|
+
const reference = pose.placement.reference;
|
|
10997
|
+
const touchesSelection = reference.kind === "anchor" && reference.objectId === selection.id || reference.kind === "lagrange" && (reference.primary === selection.id || reference.secondary === selection.id);
|
|
10998
|
+
if (touchesSelection) {
|
|
10999
|
+
pose.placement = null;
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
}
|
|
9471
11004
|
return next;
|
|
9472
11005
|
}
|
|
9473
|
-
function
|
|
11006
|
+
function findEditablePlacementOwner(document2, path, objectId) {
|
|
11007
|
+
if (path.kind === "event-pose" && path.id && path.key) {
|
|
11008
|
+
const pose = findEventPose2(document2, path.id, path.key);
|
|
11009
|
+
if (pose?.placement) {
|
|
11010
|
+
return { placement: pose.placement };
|
|
11011
|
+
}
|
|
11012
|
+
return null;
|
|
11013
|
+
}
|
|
11014
|
+
const object = findObject2(document2, objectId);
|
|
11015
|
+
if (object?.placement) {
|
|
11016
|
+
return { placement: object.placement };
|
|
11017
|
+
}
|
|
11018
|
+
return null;
|
|
11019
|
+
}
|
|
11020
|
+
function updateOrbitPhase(document2, path, objectId, details, pointer) {
|
|
9474
11021
|
const orbit = details.orbit;
|
|
9475
11022
|
if (!orbit || details.object.placement?.mode !== "orbit") {
|
|
9476
11023
|
return document2;
|
|
@@ -9481,17 +11028,17 @@
|
|
|
9481
11028
|
const radians = Math.atan2((unrotated.y - orbit.cy) / Math.max(ry, 1), (unrotated.x - orbit.cx) / Math.max(rx, 1));
|
|
9482
11029
|
const phaseDeg = normalizeDegrees(radians * 180 / Math.PI);
|
|
9483
11030
|
const next = cloneAtlasDocument(document2);
|
|
9484
|
-
const
|
|
9485
|
-
if (!
|
|
11031
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
11032
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
9486
11033
|
return document2;
|
|
9487
11034
|
}
|
|
9488
|
-
|
|
11035
|
+
placementOwner.placement.phase = {
|
|
9489
11036
|
value: roundNumber(phaseDeg, 2),
|
|
9490
11037
|
unit: "deg"
|
|
9491
11038
|
};
|
|
9492
11039
|
return next;
|
|
9493
11040
|
}
|
|
9494
|
-
function updateOrbitRadius(document2, objectId, details, pointer, dragContext) {
|
|
11041
|
+
function updateOrbitRadius(document2, path, objectId, details, pointer, dragContext) {
|
|
9495
11042
|
const orbit = details.orbit;
|
|
9496
11043
|
if (!orbit || details.object.placement?.mode !== "orbit" || !dragContext) {
|
|
9497
11044
|
return document2;
|
|
@@ -9501,47 +11048,47 @@
|
|
|
9501
11048
|
const nextBaseRadius = Math.max(nextDisplayedRadius - dragContext.radiusOffsetPx, dragContext.innerPx);
|
|
9502
11049
|
const nextMetric = orbitRadiusPxToMetric(nextBaseRadius, dragContext.innerPx, dragContext.stepPx);
|
|
9503
11050
|
const next = cloneAtlasDocument(document2);
|
|
9504
|
-
const
|
|
9505
|
-
if (!
|
|
11051
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
11052
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
9506
11053
|
return document2;
|
|
9507
11054
|
}
|
|
9508
|
-
const currentValue =
|
|
11055
|
+
const currentValue = placementOwner.placement.semiMajor ?? placementOwner.placement.distance ?? {
|
|
9509
11056
|
value: 1,
|
|
9510
11057
|
unit: "au"
|
|
9511
11058
|
};
|
|
9512
11059
|
const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
|
|
9513
|
-
if (
|
|
9514
|
-
|
|
11060
|
+
if (placementOwner.placement.semiMajor) {
|
|
11061
|
+
placementOwner.placement.semiMajor = scaled;
|
|
9515
11062
|
} else {
|
|
9516
|
-
|
|
11063
|
+
placementOwner.placement.distance = scaled;
|
|
9517
11064
|
}
|
|
9518
11065
|
return next;
|
|
9519
11066
|
}
|
|
9520
|
-
function updateAtReference(document2, objectId, scene, pointer) {
|
|
11067
|
+
function updateAtReference(document2, path, objectId, scene, pointer) {
|
|
9521
11068
|
const candidate = findNearestAtCandidate(scene, objectId, pointer);
|
|
9522
11069
|
if (!candidate) {
|
|
9523
11070
|
return document2;
|
|
9524
11071
|
}
|
|
9525
11072
|
const next = cloneAtlasDocument(document2);
|
|
9526
|
-
const
|
|
9527
|
-
if (!
|
|
11073
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
11074
|
+
if (!placementOwner || placementOwner.placement.mode !== "at") {
|
|
9528
11075
|
return document2;
|
|
9529
11076
|
}
|
|
9530
|
-
|
|
9531
|
-
|
|
11077
|
+
placementOwner.placement.reference = candidate.reference;
|
|
11078
|
+
placementOwner.placement.target = formatAtReference2(candidate.reference);
|
|
9532
11079
|
return next;
|
|
9533
11080
|
}
|
|
9534
|
-
function updateSurfaceTarget(document2, objectId, scene, pointer) {
|
|
11081
|
+
function updateSurfaceTarget(document2, path, objectId, scene, pointer) {
|
|
9535
11082
|
const target = findNearestSceneObject(scene, objectId, pointer, (entry) => SURFACE_TARGET_TYPES3.has(entry.object.type));
|
|
9536
11083
|
if (!target) {
|
|
9537
11084
|
return document2;
|
|
9538
11085
|
}
|
|
9539
11086
|
const next = cloneAtlasDocument(document2);
|
|
9540
|
-
const
|
|
9541
|
-
if (!
|
|
11087
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
11088
|
+
if (!placementOwner || placementOwner.placement.mode !== "surface") {
|
|
9542
11089
|
return document2;
|
|
9543
11090
|
}
|
|
9544
|
-
|
|
11091
|
+
placementOwner.placement.target = target.objectId;
|
|
9545
11092
|
return next;
|
|
9546
11093
|
}
|
|
9547
11094
|
function createOrbitRadiusDragContext(document2, scene, details) {
|
|
@@ -9549,7 +11096,7 @@
|
|
|
9549
11096
|
return null;
|
|
9550
11097
|
}
|
|
9551
11098
|
const targetId = details.object.placement.target;
|
|
9552
|
-
const siblingCount =
|
|
11099
|
+
const siblingCount = scene.objects.filter((entry) => entry.object.placement?.mode === "orbit" && entry.object.placement.target === targetId && !entry.hidden).length;
|
|
9553
11100
|
const spacingFactor = layoutPresetSpacingForScene(scene.layoutPreset);
|
|
9554
11101
|
const stepPx = (siblingCount > 2 ? 54 : 64) * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
|
|
9555
11102
|
const innerPx = details.parent.radius + 56 * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
|
|
@@ -9564,28 +11111,28 @@
|
|
|
9564
11111
|
preferredUnit: currentValue?.unit ?? null
|
|
9565
11112
|
};
|
|
9566
11113
|
}
|
|
9567
|
-
function updateFreeDistance(document2, objectId, scene, details, pointer) {
|
|
11114
|
+
function updateFreeDistance(document2, path, objectId, scene, details, pointer) {
|
|
9568
11115
|
if (details.object.placement?.mode !== "free") {
|
|
9569
11116
|
return document2;
|
|
9570
11117
|
}
|
|
9571
11118
|
const railX = scene.width - scene.padding - 140;
|
|
9572
11119
|
const offsetPx = Math.max(0, railX - pointer.x);
|
|
9573
11120
|
const next = cloneAtlasDocument(document2);
|
|
9574
|
-
const
|
|
9575
|
-
if (!
|
|
11121
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
11122
|
+
if (!placementOwner || placementOwner.placement.mode !== "free") {
|
|
9576
11123
|
return document2;
|
|
9577
11124
|
}
|
|
9578
|
-
const preferredUnit = normalizeFreeDistanceUnit(
|
|
11125
|
+
const preferredUnit = normalizeFreeDistanceUnit(placementOwner.placement.distance?.unit ?? null);
|
|
9579
11126
|
const metric = offsetPx / Math.max(FREE_DISTANCE_PIXEL_FACTOR * scene.scaleModel.freePlacementMultiplier, 1);
|
|
9580
11127
|
if (metric < 0.01) {
|
|
9581
|
-
|
|
9582
|
-
if (!
|
|
9583
|
-
delete
|
|
11128
|
+
placementOwner.placement.distance = void 0;
|
|
11129
|
+
if (!placementOwner.placement.descriptor) {
|
|
11130
|
+
delete placementOwner.placement.descriptor;
|
|
9584
11131
|
}
|
|
9585
11132
|
return next;
|
|
9586
11133
|
}
|
|
9587
|
-
|
|
9588
|
-
delete
|
|
11134
|
+
placementOwner.placement.distance = distanceMetricToUnitValue(metric, preferredUnit);
|
|
11135
|
+
delete placementOwner.placement.descriptor;
|
|
9589
11136
|
return next;
|
|
9590
11137
|
}
|
|
9591
11138
|
function findNearestSceneObject(scene, selectedObjectId, pointer, predicate = () => true) {
|
|
@@ -9857,9 +11404,85 @@
|
|
|
9857
11404
|
return ["viewpoint-zoom"];
|
|
9858
11405
|
case "rotationDeg":
|
|
9859
11406
|
return ["viewpoint-rotation"];
|
|
11407
|
+
case "camera":
|
|
11408
|
+
return [
|
|
11409
|
+
"viewpoint-camera-azimuth",
|
|
11410
|
+
"viewpoint-camera-elevation",
|
|
11411
|
+
"viewpoint-camera-roll",
|
|
11412
|
+
"viewpoint-camera-distance"
|
|
11413
|
+
];
|
|
11414
|
+
case "camera.azimuth":
|
|
11415
|
+
return ["viewpoint-camera-azimuth"];
|
|
11416
|
+
case "camera.elevation":
|
|
11417
|
+
return ["viewpoint-camera-elevation"];
|
|
11418
|
+
case "camera.roll":
|
|
11419
|
+
return ["viewpoint-camera-roll"];
|
|
11420
|
+
case "camera.distance":
|
|
11421
|
+
return ["viewpoint-camera-distance"];
|
|
11422
|
+
case "events":
|
|
11423
|
+
return ["viewpoint-events"];
|
|
11424
|
+
default:
|
|
11425
|
+
return [];
|
|
11426
|
+
}
|
|
11427
|
+
case "event":
|
|
11428
|
+
switch (field) {
|
|
11429
|
+
case "id":
|
|
11430
|
+
return ["event-id"];
|
|
11431
|
+
case "kind":
|
|
11432
|
+
return ["event-kind"];
|
|
11433
|
+
case "label":
|
|
11434
|
+
return ["event-label"];
|
|
11435
|
+
case "summary":
|
|
11436
|
+
return ["event-summary"];
|
|
11437
|
+
case "targetObjectId":
|
|
11438
|
+
case "target":
|
|
11439
|
+
return ["event-target"];
|
|
11440
|
+
case "participantObjectIds":
|
|
11441
|
+
case "participants":
|
|
11442
|
+
return ["event-participants"];
|
|
11443
|
+
case "timing":
|
|
11444
|
+
return ["event-timing"];
|
|
11445
|
+
case "visibility":
|
|
11446
|
+
return ["event-visibility"];
|
|
11447
|
+
case "epoch":
|
|
11448
|
+
return ["event-epoch"];
|
|
11449
|
+
case "referencePlane":
|
|
11450
|
+
return ["event-referencePlane"];
|
|
11451
|
+
case "tags":
|
|
11452
|
+
return ["event-tags"];
|
|
11453
|
+
case "color":
|
|
11454
|
+
return ["event-color"];
|
|
11455
|
+
case "hidden":
|
|
11456
|
+
return ["event-hidden"];
|
|
9860
11457
|
default:
|
|
9861
11458
|
return [];
|
|
9862
11459
|
}
|
|
11460
|
+
case "event-pose":
|
|
11461
|
+
if (field === "objectId") {
|
|
11462
|
+
return ["pose-object-id"];
|
|
11463
|
+
}
|
|
11464
|
+
if (field === "placement") {
|
|
11465
|
+
return ["placement-mode"];
|
|
11466
|
+
}
|
|
11467
|
+
if (field === "reference" || field === "target") {
|
|
11468
|
+
return ["placement-target"];
|
|
11469
|
+
}
|
|
11470
|
+
if (field === "descriptor") {
|
|
11471
|
+
return ["placement-free"];
|
|
11472
|
+
}
|
|
11473
|
+
if (PLACEMENT_DIAGNOSTIC_FIELDS.has(field)) {
|
|
11474
|
+
return [`placement-${field}`];
|
|
11475
|
+
}
|
|
11476
|
+
if (field === "inner" || field === "outer") {
|
|
11477
|
+
return [`prop-${field}`];
|
|
11478
|
+
}
|
|
11479
|
+
if (field === "epoch") {
|
|
11480
|
+
return ["pose-epoch"];
|
|
11481
|
+
}
|
|
11482
|
+
if (field === "referencePlane") {
|
|
11483
|
+
return ["pose-referencePlane"];
|
|
11484
|
+
}
|
|
11485
|
+
return [];
|
|
9863
11486
|
case "annotation":
|
|
9864
11487
|
switch (field) {
|
|
9865
11488
|
case "id":
|
|
@@ -9945,6 +11568,10 @@
|
|
|
9945
11568
|
return `Metadata: ${path.key ?? ""}`;
|
|
9946
11569
|
case "group":
|
|
9947
11570
|
return `Group: ${path.id ?? ""}`;
|
|
11571
|
+
case "event":
|
|
11572
|
+
return `Event: ${path.id ?? ""}`;
|
|
11573
|
+
case "event-pose":
|
|
11574
|
+
return `Event Pose: ${path.id ?? ""} / ${path.key ?? ""}`;
|
|
9948
11575
|
case "object":
|
|
9949
11576
|
return `Object: ${path.id ?? ""}`;
|
|
9950
11577
|
case "viewpoint":
|
|
@@ -9956,11 +11583,70 @@
|
|
|
9956
11583
|
}
|
|
9957
11584
|
}
|
|
9958
11585
|
function selectionKey(path) {
|
|
9959
|
-
return path ? `${path.kind}:${path.id ?? path.key ?? ""}` : null;
|
|
11586
|
+
return path ? `${path.kind}:${path.id ?? ""}:${path.key ?? ""}` : null;
|
|
11587
|
+
}
|
|
11588
|
+
function selectionEventId(path) {
|
|
11589
|
+
if (!path) {
|
|
11590
|
+
return null;
|
|
11591
|
+
}
|
|
11592
|
+
return path.kind === "event" || path.kind === "event-pose" ? path.id ?? null : null;
|
|
9960
11593
|
}
|
|
9961
11594
|
function compareObjects2(left, right) {
|
|
9962
11595
|
return left.id.localeCompare(right.id);
|
|
9963
11596
|
}
|
|
11597
|
+
function compareEvents(left, right) {
|
|
11598
|
+
return left.id.localeCompare(right.id);
|
|
11599
|
+
}
|
|
11600
|
+
function compareEventPoses(left, right) {
|
|
11601
|
+
return left.objectId.localeCompare(right.objectId);
|
|
11602
|
+
}
|
|
11603
|
+
function findEvent2(document2, eventId) {
|
|
11604
|
+
return document2.events.find((entry) => entry.id === eventId) ?? null;
|
|
11605
|
+
}
|
|
11606
|
+
function findEventPose2(document2, eventId, objectId) {
|
|
11607
|
+
return findEvent2(document2, eventId)?.positions.find((entry) => entry.objectId === objectId) ?? null;
|
|
11608
|
+
}
|
|
11609
|
+
function findObject2(document2, objectId) {
|
|
11610
|
+
return document2.objects.find((entry) => entry.id === objectId) ?? null;
|
|
11611
|
+
}
|
|
11612
|
+
function addEventPose(document2, eventId) {
|
|
11613
|
+
const next = cloneAtlasDocument(document2);
|
|
11614
|
+
const eventEntry = next.events.find((entry) => entry.id === eventId);
|
|
11615
|
+
if (!eventEntry) {
|
|
11616
|
+
return document2;
|
|
11617
|
+
}
|
|
11618
|
+
const baseObject = next.objects.find((object) => !eventEntry.positions.some((pose) => pose.objectId === object.id)) ?? next.objects[0];
|
|
11619
|
+
if (!baseObject) {
|
|
11620
|
+
return document2;
|
|
11621
|
+
}
|
|
11622
|
+
if (eventEntry.targetObjectId !== baseObject.id && !eventEntry.participantObjectIds.includes(baseObject.id)) {
|
|
11623
|
+
eventEntry.participantObjectIds.push(baseObject.id);
|
|
11624
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
11625
|
+
}
|
|
11626
|
+
eventEntry.positions.push(createEventPoseFromObject(baseObject));
|
|
11627
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
11628
|
+
return next;
|
|
11629
|
+
}
|
|
11630
|
+
function createEventPoseFromObject(object) {
|
|
11631
|
+
return {
|
|
11632
|
+
objectId: object.id,
|
|
11633
|
+
placement: object.placement ? structuredClone(object.placement) : null,
|
|
11634
|
+
inner: readUnitValue(object.properties.inner),
|
|
11635
|
+
outer: readUnitValue(object.properties.outer)
|
|
11636
|
+
};
|
|
11637
|
+
}
|
|
11638
|
+
function syncEventViewpointReferences(document2, previousEventId, nextEventId, viewpointIds) {
|
|
11639
|
+
const desired = new Set(viewpointIds);
|
|
11640
|
+
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
11641
|
+
const currentIds = new Set(viewpoint.events);
|
|
11642
|
+
currentIds.delete(previousEventId);
|
|
11643
|
+
currentIds.delete(nextEventId);
|
|
11644
|
+
if (desired.has(viewpoint.id)) {
|
|
11645
|
+
currentIds.add(nextEventId);
|
|
11646
|
+
}
|
|
11647
|
+
viewpoint.events = [...currentIds].sort((left, right) => left.localeCompare(right));
|
|
11648
|
+
}
|
|
11649
|
+
}
|
|
9964
11650
|
function createUniqueId(prefix, existing) {
|
|
9965
11651
|
const safePrefix = prefix.trim() || "item";
|
|
9966
11652
|
let counter = 1;
|
|
@@ -9989,6 +11675,9 @@
|
|
|
9989
11675
|
function readUnitProperty(value) {
|
|
9990
11676
|
return value && typeof value === "object" && "value" in value ? formatUnitValue3(value) : "";
|
|
9991
11677
|
}
|
|
11678
|
+
function readUnitValue(value) {
|
|
11679
|
+
return value && typeof value === "object" && "value" in value ? value : void 0;
|
|
11680
|
+
}
|
|
9992
11681
|
function readNumberProperty(value) {
|
|
9993
11682
|
return typeof value === "number" ? String(value) : "";
|
|
9994
11683
|
}
|
|
@@ -10294,6 +11983,12 @@
|
|
|
10294
11983
|
.wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
|
|
10295
11984
|
.wo-editor-outline { display: grid; gap: 14px; }
|
|
10296
11985
|
.wo-editor-outline-section { display: grid; gap: 8px; }
|
|
11986
|
+
.wo-editor-outline-group { display: grid; gap: 6px; }
|
|
11987
|
+
.wo-editor-outline-children {
|
|
11988
|
+
display: grid;
|
|
11989
|
+
gap: 6px;
|
|
11990
|
+
padding-left: 16px;
|
|
11991
|
+
}
|
|
10297
11992
|
.wo-editor-outline-section h3 {
|
|
10298
11993
|
margin: 0;
|
|
10299
11994
|
color: rgba(237,246,255,0.68);
|
|
@@ -10333,6 +12028,27 @@
|
|
|
10333
12028
|
background: rgba(255, 120, 120, 0.18);
|
|
10334
12029
|
color: #ffb2b2;
|
|
10335
12030
|
}
|
|
12031
|
+
.wo-editor-inline-list { display: grid; gap: 8px; }
|
|
12032
|
+
.wo-editor-inline-actions {
|
|
12033
|
+
display: flex;
|
|
12034
|
+
flex-wrap: wrap;
|
|
12035
|
+
gap: 10px;
|
|
12036
|
+
margin-top: 12px;
|
|
12037
|
+
}
|
|
12038
|
+
.wo-editor-inline-actions button {
|
|
12039
|
+
border: 1px solid rgba(255,255,255,0.14);
|
|
12040
|
+
border-radius: 999px;
|
|
12041
|
+
background: rgba(255,255,255,0.06);
|
|
12042
|
+
color: #edf6ff;
|
|
12043
|
+
cursor: pointer;
|
|
12044
|
+
font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
12045
|
+
padding: 8px 12px;
|
|
12046
|
+
}
|
|
12047
|
+
.wo-editor-inline-note {
|
|
12048
|
+
margin: 0 0 12px;
|
|
12049
|
+
color: rgba(237,246,255,0.72);
|
|
12050
|
+
font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
12051
|
+
}
|
|
10336
12052
|
.wo-editor-diagnostics { display: grid; gap: 10px; }
|
|
10337
12053
|
.wo-editor-diagnostic {
|
|
10338
12054
|
display: grid;
|