worldorbit 2.5.13 → 2.5.16
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 +37 -11
- package/dist/browser/core/dist/index.js +1811 -386
- package/dist/browser/editor/dist/index.js +10534 -0
- package/dist/browser/markdown/dist/index.js +1477 -221
- package/dist/browser/viewer/dist/index.js +1569 -230
- package/dist/unpkg/core/dist/index.js +1814 -389
- package/dist/unpkg/editor/dist/index.js +10559 -0
- package/dist/unpkg/markdown/dist/index.js +1480 -224
- package/dist/unpkg/viewer/dist/index.js +1572 -233
- package/dist/unpkg/worldorbit-core.min.js +12 -5
- package/dist/unpkg/worldorbit-editor.min.js +812 -0
- package/dist/unpkg/worldorbit-markdown.min.js +32 -23
- package/dist/unpkg/worldorbit-viewer.min.js +55 -41
- package/dist/unpkg/worldorbit.js +1713 -231
- package/dist/unpkg/worldorbit.min.js +58 -44
- package/package.json +3 -2
- package/packages/core/README.md +5 -1
- package/packages/core/dist/atlas-edit.d.ts +2 -2
- package/packages/core/dist/atlas-edit.js +70 -7
- package/packages/core/dist/atlas-utils.d.ts +22 -0
- package/packages/core/dist/atlas-utils.js +189 -0
- package/packages/core/dist/atlas-validate.d.ts +2 -0
- package/packages/core/dist/atlas-validate.js +285 -0
- package/packages/core/dist/draft-parse.js +786 -153
- package/packages/core/dist/draft.d.ts +3 -0
- package/packages/core/dist/draft.js +47 -3
- package/packages/core/dist/format.js +165 -9
- package/packages/core/dist/load.js +58 -13
- package/packages/core/dist/normalize.js +7 -0
- package/packages/core/dist/scene.js +66 -13
- package/packages/core/dist/types.d.ts +97 -3
- package/packages/editor/dist/editor.js +44 -0
- package/packages/markdown/README.md +1 -1
- package/packages/viewer/README.md +2 -1
- package/packages/viewer/dist/atlas-state.js +7 -1
- package/packages/viewer/dist/atlas-viewer.js +35 -1
- package/packages/viewer/dist/render.js +16 -7
- package/packages/viewer/dist/theme.js +4 -0
- package/packages/viewer/dist/tooltip.js +35 -0
- package/packages/viewer/dist/types.d.ts +7 -0
- package/packages/viewer/dist/viewer.js +4 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var DEFAULT_LAYERS = {
|
|
5
5
|
background: true,
|
|
6
6
|
guides: true,
|
|
7
|
+
relations: true,
|
|
7
8
|
orbits: true,
|
|
8
9
|
objects: true,
|
|
9
10
|
labels: true,
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
backgroundGlow: "rgba(240, 180, 100, 0.18)",
|
|
19
20
|
panel: "rgba(7, 17, 27, 0.9)",
|
|
20
21
|
panelLine: "rgba(168, 207, 242, 0.18)",
|
|
22
|
+
relation: "rgba(240, 180, 100, 0.42)",
|
|
21
23
|
orbit: "rgba(163, 209, 255, 0.24)",
|
|
22
24
|
orbitBand: "rgba(255, 190, 120, 0.28)",
|
|
23
25
|
guide: "rgba(255, 255, 255, 0.04)",
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
backgroundGlow: "rgba(120, 255, 215, 0.16)",
|
|
41
43
|
panel: "rgba(7, 20, 30, 0.9)",
|
|
42
44
|
panelLine: "rgba(120, 255, 215, 0.16)",
|
|
45
|
+
relation: "rgba(156, 231, 255, 0.42)",
|
|
43
46
|
orbit: "rgba(120, 255, 215, 0.2)",
|
|
44
47
|
orbitBand: "rgba(137, 185, 255, 0.24)",
|
|
45
48
|
guide: "rgba(255, 255, 255, 0.035)",
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
backgroundGlow: "rgba(255, 127, 95, 0.18)",
|
|
63
66
|
panel: "rgba(24, 9, 13, 0.9)",
|
|
64
67
|
panelLine: "rgba(255, 166, 149, 0.16)",
|
|
68
|
+
relation: "rgba(255, 178, 125, 0.42)",
|
|
65
69
|
orbit: "rgba(255, 188, 164, 0.22)",
|
|
66
70
|
orbitBand: "rgba(255, 214, 139, 0.24)",
|
|
67
71
|
guide: "rgba(255, 255, 255, 0.03)",
|
|
@@ -214,6 +218,7 @@
|
|
|
214
218
|
return {
|
|
215
219
|
background: viewpoint.layers.background,
|
|
216
220
|
guides: viewpoint.layers.guides,
|
|
221
|
+
relations: viewpoint.layers.relations,
|
|
217
222
|
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,
|
|
218
223
|
objects: viewpoint.layers.objects,
|
|
219
224
|
labels: viewpoint.layers.labels,
|
|
@@ -251,7 +256,11 @@
|
|
|
251
256
|
return false;
|
|
252
257
|
}
|
|
253
258
|
if (filter.groupIds?.length && (!object.groupId || !filter.groupIds.includes(object.groupId))) {
|
|
254
|
-
|
|
259
|
+
const hasSemanticMatch = object.semanticGroupIds.length > 0 && filter.groupIds.some((groupId) => object.semanticGroupIds.includes(groupId));
|
|
260
|
+
const hasLegacyMatch = Boolean(object.groupId && filter.groupIds.includes(object.groupId));
|
|
261
|
+
if (!hasSemanticMatch && !hasLegacyMatch) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
255
264
|
}
|
|
256
265
|
if (filter.tags?.length) {
|
|
257
266
|
const objectTags = Array.isArray(object.object.properties.tags) ? object.object.properties.tags.filter((entry) => typeof entry === "string") : [];
|
|
@@ -618,13 +627,13 @@
|
|
|
618
627
|
function unitFamilyAllowsUnit(family, unit) {
|
|
619
628
|
switch (family) {
|
|
620
629
|
case "distance":
|
|
621
|
-
return unit === null || ["au", "km", "re", "sol"].includes(unit);
|
|
630
|
+
return unit === null || ["au", "km", "m", "ly", "pc", "kpc", "re", "sol"].includes(unit);
|
|
622
631
|
case "radius":
|
|
623
|
-
return unit === null || ["km", "re", "sol"].includes(unit);
|
|
632
|
+
return unit === null || ["km", "m", "re", "rj", "sol"].includes(unit);
|
|
624
633
|
case "mass":
|
|
625
|
-
return unit === null || ["me", "sol"].includes(unit);
|
|
634
|
+
return unit === null || ["me", "mj", "sol"].includes(unit);
|
|
626
635
|
case "duration":
|
|
627
|
-
return unit === null || ["h", "d", "y"].includes(unit);
|
|
636
|
+
return unit === null || ["s", "min", "h", "d", "y", "ky", "my", "gy"].includes(unit);
|
|
628
637
|
case "angle":
|
|
629
638
|
return unit === null || unit === "deg";
|
|
630
639
|
case "generic":
|
|
@@ -828,7 +837,7 @@
|
|
|
828
837
|
}
|
|
829
838
|
|
|
830
839
|
// packages/core/dist/normalize.js
|
|
831
|
-
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(au|km|re|sol|
|
|
840
|
+
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(kpc|min|mj|rj|ky|my|gy|au|km|me|re|pc|ly|deg|sol|K|m|s|h|d|y)?$/;
|
|
832
841
|
var BOOLEAN_VALUES = /* @__PURE__ */ new Map([
|
|
833
842
|
["true", true],
|
|
834
843
|
["false", false],
|
|
@@ -853,7 +862,10 @@
|
|
|
853
862
|
return {
|
|
854
863
|
format: "worldorbit",
|
|
855
864
|
version: "1.0",
|
|
865
|
+
schemaVersion: "1.0",
|
|
856
866
|
system,
|
|
867
|
+
groups: [],
|
|
868
|
+
relations: [],
|
|
857
869
|
objects
|
|
858
870
|
};
|
|
859
871
|
}
|
|
@@ -863,13 +875,17 @@
|
|
|
863
875
|
const fieldMap = collectFields(mergedFields);
|
|
864
876
|
const placement = extractPlacement(node.objectType, fieldMap);
|
|
865
877
|
const properties = normalizeProperties(fieldMap);
|
|
866
|
-
const
|
|
878
|
+
const info2 = normalizeInfo(node.infoEntries);
|
|
867
879
|
if (node.objectType === "system") {
|
|
868
880
|
return {
|
|
869
881
|
type: "system",
|
|
870
882
|
id: node.name,
|
|
883
|
+
title: typeof properties.title === "string" ? properties.title : null,
|
|
884
|
+
description: null,
|
|
885
|
+
epoch: null,
|
|
886
|
+
referencePlane: null,
|
|
871
887
|
properties,
|
|
872
|
-
info
|
|
888
|
+
info: info2
|
|
873
889
|
};
|
|
874
890
|
}
|
|
875
891
|
return {
|
|
@@ -877,7 +893,7 @@
|
|
|
877
893
|
id: node.name,
|
|
878
894
|
properties,
|
|
879
895
|
placement,
|
|
880
|
-
info
|
|
896
|
+
info: info2
|
|
881
897
|
};
|
|
882
898
|
}
|
|
883
899
|
function validateFieldCompatibility(objectType, fields) {
|
|
@@ -1007,14 +1023,14 @@
|
|
|
1007
1023
|
}
|
|
1008
1024
|
}
|
|
1009
1025
|
function normalizeInfo(entries) {
|
|
1010
|
-
const
|
|
1026
|
+
const info2 = {};
|
|
1011
1027
|
for (const entry of entries) {
|
|
1012
|
-
if (entry.key in
|
|
1028
|
+
if (entry.key in info2) {
|
|
1013
1029
|
throw WorldOrbitError.fromLocation(`Duplicate info key "${entry.key}"`, entry.location);
|
|
1014
1030
|
}
|
|
1015
|
-
|
|
1031
|
+
info2[entry.key] = entry.value;
|
|
1016
1032
|
}
|
|
1017
|
-
return
|
|
1033
|
+
return info2;
|
|
1018
1034
|
}
|
|
1019
1035
|
function parseAtReference(target, location) {
|
|
1020
1036
|
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
@@ -1185,37 +1201,41 @@
|
|
|
1185
1201
|
}
|
|
1186
1202
|
|
|
1187
1203
|
// packages/core/dist/diagnostics.js
|
|
1188
|
-
function diagnosticFromError(
|
|
1189
|
-
if (
|
|
1204
|
+
function diagnosticFromError(error2, source, code = `${source}.failed`) {
|
|
1205
|
+
if (error2 instanceof WorldOrbitError) {
|
|
1190
1206
|
return {
|
|
1191
1207
|
code,
|
|
1192
1208
|
severity: "error",
|
|
1193
1209
|
source,
|
|
1194
|
-
message:
|
|
1195
|
-
line:
|
|
1196
|
-
column:
|
|
1210
|
+
message: error2.message,
|
|
1211
|
+
line: error2.line,
|
|
1212
|
+
column: error2.column
|
|
1197
1213
|
};
|
|
1198
1214
|
}
|
|
1199
|
-
if (
|
|
1215
|
+
if (error2 instanceof Error) {
|
|
1200
1216
|
return {
|
|
1201
1217
|
code,
|
|
1202
1218
|
severity: "error",
|
|
1203
1219
|
source,
|
|
1204
|
-
message:
|
|
1220
|
+
message: error2.message
|
|
1205
1221
|
};
|
|
1206
1222
|
}
|
|
1207
1223
|
return {
|
|
1208
1224
|
code,
|
|
1209
1225
|
severity: "error",
|
|
1210
1226
|
source,
|
|
1211
|
-
message: String(
|
|
1227
|
+
message: String(error2)
|
|
1212
1228
|
};
|
|
1213
1229
|
}
|
|
1214
1230
|
|
|
1215
1231
|
// packages/core/dist/scene.js
|
|
1216
1232
|
var AU_IN_KM = 1495978707e-1;
|
|
1217
1233
|
var EARTH_RADIUS_IN_KM = 6371;
|
|
1234
|
+
var JUPITER_RADIUS_IN_KM = 71492;
|
|
1218
1235
|
var SOLAR_RADIUS_IN_KM = 695700;
|
|
1236
|
+
var LY_IN_AU = 63241.077;
|
|
1237
|
+
var PC_IN_AU = 206264.806;
|
|
1238
|
+
var KPC_IN_AU = 206264806;
|
|
1219
1239
|
var ISO_FLATTENING = 0.68;
|
|
1220
1240
|
var MIN_ISO_MINOR_SCALE = 0.2;
|
|
1221
1241
|
var ARC_SAMPLE_COUNT = 28;
|
|
@@ -1335,8 +1355,10 @@
|
|
|
1335
1355
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1336
1356
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1337
1357
|
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1338
|
-
const
|
|
1358
|
+
const relations = createSceneRelations(document2, objects);
|
|
1359
|
+
const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
|
|
1339
1360
|
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
|
|
1361
|
+
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1340
1362
|
const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
|
|
1341
1363
|
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1342
1364
|
return {
|
|
@@ -1346,7 +1368,7 @@
|
|
|
1346
1368
|
renderPreset: frame.preset,
|
|
1347
1369
|
projection,
|
|
1348
1370
|
scaleModel,
|
|
1349
|
-
title: String(document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1371
|
+
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1350
1372
|
subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
|
|
1351
1373
|
systemId,
|
|
1352
1374
|
viewMode: projection,
|
|
@@ -1362,9 +1384,11 @@
|
|
|
1362
1384
|
contentBounds,
|
|
1363
1385
|
layers,
|
|
1364
1386
|
groups,
|
|
1387
|
+
semanticGroups,
|
|
1365
1388
|
viewpoints,
|
|
1366
1389
|
objects,
|
|
1367
1390
|
orbitVisuals,
|
|
1391
|
+
relations,
|
|
1368
1392
|
leaders,
|
|
1369
1393
|
labels
|
|
1370
1394
|
};
|
|
@@ -1474,6 +1498,7 @@
|
|
|
1474
1498
|
}
|
|
1475
1499
|
function createSceneObject(position, scaleModel, relationships) {
|
|
1476
1500
|
const { object, x, y, radius, sortKey, anchorX, anchorY } = position;
|
|
1501
|
+
const renderPriority = object.renderHints?.renderPriority ?? 0;
|
|
1477
1502
|
return {
|
|
1478
1503
|
renderId: createRenderId(object.id),
|
|
1479
1504
|
objectId: object.id,
|
|
@@ -1482,11 +1507,12 @@
|
|
|
1482
1507
|
ancestorIds: relationships.ancestorIds.get(object.id) ?? [],
|
|
1483
1508
|
childIds: relationships.childIds.get(object.id) ?? [],
|
|
1484
1509
|
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1510
|
+
semanticGroupIds: [...object.groups ?? []],
|
|
1485
1511
|
x,
|
|
1486
1512
|
y,
|
|
1487
1513
|
radius,
|
|
1488
1514
|
visualRadius: visualExtentForObject(object, radius, scaleModel),
|
|
1489
|
-
sortKey,
|
|
1515
|
+
sortKey: sortKey + renderPriority * 1e-3,
|
|
1490
1516
|
anchorX,
|
|
1491
1517
|
anchorY,
|
|
1492
1518
|
label: object.id,
|
|
@@ -1503,6 +1529,7 @@
|
|
|
1503
1529
|
object: draft.object,
|
|
1504
1530
|
parentId: draft.parentId,
|
|
1505
1531
|
groupId,
|
|
1532
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1506
1533
|
kind: draft.kind,
|
|
1507
1534
|
cx: draft.cx,
|
|
1508
1535
|
cy: draft.cy,
|
|
@@ -1514,7 +1541,7 @@
|
|
|
1514
1541
|
bandThickness: draft.bandThickness,
|
|
1515
1542
|
frontArcPath: draft.frontArcPath,
|
|
1516
1543
|
backArcPath: draft.backArcPath,
|
|
1517
|
-
hidden: draft.object.properties.hidden === true
|
|
1544
|
+
hidden: draft.object.properties.hidden === true || draft.object.renderHints?.renderOrbit === false
|
|
1518
1545
|
};
|
|
1519
1546
|
}
|
|
1520
1547
|
function createLeaderLine(draft) {
|
|
@@ -1523,6 +1550,7 @@
|
|
|
1523
1550
|
objectId: draft.object.id,
|
|
1524
1551
|
object: draft.object,
|
|
1525
1552
|
groupId: draft.groupId,
|
|
1553
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1526
1554
|
x1: draft.x1,
|
|
1527
1555
|
y1: draft.y1,
|
|
1528
1556
|
x2: draft.x2,
|
|
@@ -1534,7 +1562,7 @@
|
|
|
1534
1562
|
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1535
1563
|
const labels = [];
|
|
1536
1564
|
const occupied = [];
|
|
1537
|
-
const visibleObjects = [...objects].filter((object) => !object.hidden).sort((left, right) => left.sortKey - right.sortKey);
|
|
1565
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort((left, right) => left.sortKey - right.sortKey);
|
|
1538
1566
|
for (const object of visibleObjects) {
|
|
1539
1567
|
const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
|
|
1540
1568
|
const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
|
|
@@ -1554,6 +1582,7 @@
|
|
|
1554
1582
|
objectId: object.objectId,
|
|
1555
1583
|
object: object.object,
|
|
1556
1584
|
groupId: object.groupId,
|
|
1585
|
+
semanticGroupIds: [...object.semanticGroupIds],
|
|
1557
1586
|
label: object.label,
|
|
1558
1587
|
secondaryLabel: object.secondaryLabel,
|
|
1559
1588
|
x: object.x,
|
|
@@ -1566,7 +1595,7 @@
|
|
|
1566
1595
|
}
|
|
1567
1596
|
return labels;
|
|
1568
1597
|
}
|
|
1569
|
-
function createSceneLayers(orbitVisuals, leaders, objects, labels) {
|
|
1598
|
+
function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
|
|
1570
1599
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1571
1600
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1572
1601
|
return [
|
|
@@ -1577,6 +1606,10 @@
|
|
|
1577
1606
|
},
|
|
1578
1607
|
{ id: "orbits-back", renderIds: backOrbitIds },
|
|
1579
1608
|
{ id: "orbits-front", renderIds: frontOrbitIds },
|
|
1609
|
+
{
|
|
1610
|
+
id: "relations",
|
|
1611
|
+
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1612
|
+
},
|
|
1580
1613
|
{
|
|
1581
1614
|
id: "objects",
|
|
1582
1615
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1641,6 +1674,36 @@
|
|
|
1641
1674
|
}
|
|
1642
1675
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1643
1676
|
}
|
|
1677
|
+
function createSceneSemanticGroups(document2, objects) {
|
|
1678
|
+
return [...document2.groups].map((group) => ({
|
|
1679
|
+
id: group.id,
|
|
1680
|
+
label: group.label,
|
|
1681
|
+
summary: group.summary,
|
|
1682
|
+
color: group.color,
|
|
1683
|
+
tags: [...group.tags],
|
|
1684
|
+
hidden: group.hidden,
|
|
1685
|
+
objectIds: objects.filter((object) => !object.hidden && object.semanticGroupIds.includes(group.id)).map((object) => object.objectId)
|
|
1686
|
+
})).sort((left, right) => left.label.localeCompare(right.label));
|
|
1687
|
+
}
|
|
1688
|
+
function createSceneRelations(document2, objects) {
|
|
1689
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1690
|
+
return document2.relations.map((relation) => {
|
|
1691
|
+
const from = objectMap.get(relation.from);
|
|
1692
|
+
const to = objectMap.get(relation.to);
|
|
1693
|
+
return {
|
|
1694
|
+
renderId: `${createRenderId(relation.id)}-relation`,
|
|
1695
|
+
relationId: relation.id,
|
|
1696
|
+
relation,
|
|
1697
|
+
fromObjectId: relation.from,
|
|
1698
|
+
toObjectId: relation.to,
|
|
1699
|
+
x1: from?.x ?? 0,
|
|
1700
|
+
y1: from?.y ?? 0,
|
|
1701
|
+
x2: to?.x ?? 0,
|
|
1702
|
+
y2: to?.y ?? 0,
|
|
1703
|
+
hidden: relation.hidden || !from || !to || from.hidden || to.hidden
|
|
1704
|
+
};
|
|
1705
|
+
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1706
|
+
}
|
|
1644
1707
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1645
1708
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1646
1709
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1658,7 +1721,7 @@
|
|
|
1658
1721
|
}
|
|
1659
1722
|
const field = fieldParts.join(".").toLowerCase();
|
|
1660
1723
|
const draft = drafts.get(id) ?? { id };
|
|
1661
|
-
applyViewpointField(draft, field, value, projection, preset, relationships, objectMap);
|
|
1724
|
+
applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap);
|
|
1662
1725
|
drafts.set(id, draft);
|
|
1663
1726
|
}
|
|
1664
1727
|
const viewpoints = [...drafts.values()].map((draft) => finalizeViewpointDraft(draft, projection, preset, objectMap)).filter(Boolean);
|
|
@@ -1686,7 +1749,8 @@
|
|
|
1686
1749
|
});
|
|
1687
1750
|
}
|
|
1688
1751
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1689
|
-
const
|
|
1752
|
+
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1753
|
+
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1690
1754
|
return {
|
|
1691
1755
|
id: "overview",
|
|
1692
1756
|
label,
|
|
@@ -1702,7 +1766,7 @@
|
|
|
1702
1766
|
generated: true
|
|
1703
1767
|
};
|
|
1704
1768
|
}
|
|
1705
|
-
function applyViewpointField(draft, field, value, projection, preset, relationships, objectMap) {
|
|
1769
|
+
function applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap) {
|
|
1706
1770
|
const normalizedValue = value.trim();
|
|
1707
1771
|
switch (field) {
|
|
1708
1772
|
case "label":
|
|
@@ -1769,7 +1833,7 @@
|
|
|
1769
1833
|
case "groups":
|
|
1770
1834
|
draft.filter = {
|
|
1771
1835
|
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1772
|
-
groupIds: parseViewpointGroups(normalizedValue, relationships, objectMap)
|
|
1836
|
+
groupIds: parseViewpointGroups(normalizedValue, document2, relationships, objectMap)
|
|
1773
1837
|
};
|
|
1774
1838
|
return;
|
|
1775
1839
|
}
|
|
@@ -1842,7 +1906,7 @@
|
|
|
1842
1906
|
next["orbits-front"] = enabled;
|
|
1843
1907
|
continue;
|
|
1844
1908
|
}
|
|
1845
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1909
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1846
1910
|
next[rawLayer] = enabled;
|
|
1847
1911
|
}
|
|
1848
1912
|
}
|
|
@@ -1851,8 +1915,11 @@
|
|
|
1851
1915
|
function parseViewpointObjectTypes(value) {
|
|
1852
1916
|
return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
|
|
1853
1917
|
}
|
|
1854
|
-
function parseViewpointGroups(value, relationships, objectMap) {
|
|
1918
|
+
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1855
1919
|
return splitListValue(value).map((entry) => {
|
|
1920
|
+
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
1921
|
+
return entry;
|
|
1922
|
+
}
|
|
1856
1923
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
1857
1924
|
return entry;
|
|
1858
1925
|
}
|
|
@@ -1983,8 +2050,9 @@
|
|
|
1983
2050
|
}
|
|
1984
2051
|
const orbiting = [...context.orbitChildren.get(object.id) ?? []].sort(compareOrbiting);
|
|
1985
2052
|
const orbitMetricContext = computeOrbitMetricContext(orbiting, parent.radius, context.spacingFactor, context.scaleModel);
|
|
2053
|
+
const orbitRadiiPx = resolveOrbitRadiiPx(orbiting, orbitMetricContext);
|
|
1986
2054
|
orbiting.forEach((child, index) => {
|
|
1987
|
-
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, context);
|
|
2055
|
+
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, orbitRadiiPx[index] ?? orbitMetricContext.innerPx, context);
|
|
1988
2056
|
orbitDrafts.push({
|
|
1989
2057
|
object: child,
|
|
1990
2058
|
parentId: object.id,
|
|
@@ -2058,7 +2126,8 @@
|
|
|
2058
2126
|
metricSpread: 0,
|
|
2059
2127
|
innerPx,
|
|
2060
2128
|
stepPx,
|
|
2061
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
2129
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
2130
|
+
minimumGapPx: stepPx * 0.42
|
|
2062
2131
|
};
|
|
2063
2132
|
}
|
|
2064
2133
|
const minMetric = Math.min(...presentMetrics);
|
|
@@ -2071,10 +2140,11 @@
|
|
|
2071
2140
|
metricSpread,
|
|
2072
2141
|
innerPx,
|
|
2073
2142
|
stepPx,
|
|
2074
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
2143
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
2144
|
+
minimumGapPx: stepPx * 0.42
|
|
2075
2145
|
};
|
|
2076
2146
|
}
|
|
2077
|
-
function resolveOrbitGeometry(object, index, count, parent, metricContext, context) {
|
|
2147
|
+
function resolveOrbitGeometry(object, index, count, parent, metricContext, orbitRadiusPx, context) {
|
|
2078
2148
|
const placement = object.placement;
|
|
2079
2149
|
const band = object.type === "belt" || object.type === "ring";
|
|
2080
2150
|
if (!placement || placement.mode !== "orbit") {
|
|
@@ -2092,7 +2162,7 @@
|
|
|
2092
2162
|
};
|
|
2093
2163
|
}
|
|
2094
2164
|
const eccentricity = clampNumber(typeof placement.eccentricity === "number" ? placement.eccentricity : 0, 0, 0.92);
|
|
2095
|
-
const semiMajor =
|
|
2165
|
+
const semiMajor = orbitRadiusPx;
|
|
2096
2166
|
const baseMinor = Math.max(semiMajor * Math.sqrt(1 - eccentricity * eccentricity), semiMajor * 0.18);
|
|
2097
2167
|
const inclinationDeg = unitValueToDegrees(placement.inclination) ?? 0;
|
|
2098
2168
|
const inclinationScale = context.projection === "isometric" ? Math.max(MIN_ISO_MINOR_SCALE, Math.cos(degreesToRadians(inclinationDeg))) * ISO_FLATTENING : 1;
|
|
@@ -2122,15 +2192,19 @@
|
|
|
2122
2192
|
objectY: objectPoint.y
|
|
2123
2193
|
};
|
|
2124
2194
|
}
|
|
2125
|
-
function resolveOrbitRadiusPx(
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2195
|
+
function resolveOrbitRadiusPx(metric, metricContext) {
|
|
2196
|
+
return metricContext.innerPx + metricContext.stepPx * log2(Math.max(metric, 0) + 1);
|
|
2197
|
+
}
|
|
2198
|
+
function resolveOrbitRadiiPx(objects, metricContext) {
|
|
2199
|
+
const radii = [];
|
|
2200
|
+
objects.forEach((object, index) => {
|
|
2201
|
+
const metric = orbitMetric(object);
|
|
2202
|
+
const fallbackRadius = metricContext.innerPx + index * metricContext.stepPx;
|
|
2203
|
+
const baseRadius = metric === null ? fallbackRadius : resolveOrbitRadiusPx(metric, metricContext);
|
|
2204
|
+
const minimumRadius = index === 0 ? metricContext.innerPx : (radii[index - 1] ?? metricContext.innerPx) + metricContext.minimumGapPx;
|
|
2205
|
+
radii.push(Math.max(baseRadius, minimumRadius));
|
|
2206
|
+
});
|
|
2207
|
+
return radii;
|
|
2134
2208
|
}
|
|
2135
2209
|
function orbitMetric(object) {
|
|
2136
2210
|
if (!object.placement || object.placement.mode !== "orbit") {
|
|
@@ -2138,6 +2212,9 @@
|
|
|
2138
2212
|
}
|
|
2139
2213
|
return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
|
|
2140
2214
|
}
|
|
2215
|
+
function log2(value) {
|
|
2216
|
+
return Math.log(value) / Math.log(2);
|
|
2217
|
+
}
|
|
2141
2218
|
function resolveOrbitPhase(phase, index, count) {
|
|
2142
2219
|
const degreeValue = phase ? unitValueToDegrees(phase) : null;
|
|
2143
2220
|
if (degreeValue !== null) {
|
|
@@ -2461,8 +2538,18 @@
|
|
|
2461
2538
|
return value.value;
|
|
2462
2539
|
case "km":
|
|
2463
2540
|
return value.value / AU_IN_KM;
|
|
2541
|
+
case "m":
|
|
2542
|
+
return value.value / 1e3 / AU_IN_KM;
|
|
2543
|
+
case "ly":
|
|
2544
|
+
return value.value * LY_IN_AU;
|
|
2545
|
+
case "pc":
|
|
2546
|
+
return value.value * PC_IN_AU;
|
|
2547
|
+
case "kpc":
|
|
2548
|
+
return value.value * KPC_IN_AU;
|
|
2464
2549
|
case "re":
|
|
2465
2550
|
return value.value * EARTH_RADIUS_IN_KM / AU_IN_KM;
|
|
2551
|
+
case "rj":
|
|
2552
|
+
return value.value * JUPITER_RADIUS_IN_KM / AU_IN_KM;
|
|
2466
2553
|
case "sol":
|
|
2467
2554
|
return value.value * SOLAR_RADIUS_IN_KM / AU_IN_KM;
|
|
2468
2555
|
default:
|
|
@@ -2602,19 +2689,37 @@
|
|
|
2602
2689
|
const system = document2.system ? {
|
|
2603
2690
|
type: "system",
|
|
2604
2691
|
id: document2.system.id,
|
|
2692
|
+
title: document2.system.title,
|
|
2693
|
+
description: document2.system.description,
|
|
2694
|
+
epoch: document2.system.epoch,
|
|
2695
|
+
referencePlane: document2.system.referencePlane,
|
|
2605
2696
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2606
2697
|
info: materializeDraftSystemInfo(document2.system)
|
|
2607
2698
|
} : null;
|
|
2608
2699
|
return {
|
|
2609
2700
|
format: "worldorbit",
|
|
2610
2701
|
version: "1.0",
|
|
2702
|
+
schemaVersion: document2.version,
|
|
2611
2703
|
system,
|
|
2704
|
+
groups: structuredClone(document2.groups ?? []),
|
|
2705
|
+
relations: structuredClone(document2.relations ?? []),
|
|
2612
2706
|
objects: document2.objects.map(cloneWorldOrbitObject)
|
|
2613
2707
|
};
|
|
2614
2708
|
}
|
|
2615
2709
|
function cloneWorldOrbitObject(object) {
|
|
2616
2710
|
return {
|
|
2617
2711
|
...object,
|
|
2712
|
+
groups: object.groups ? [...object.groups] : void 0,
|
|
2713
|
+
resonance: object.resonance ? { ...object.resonance } : object.resonance,
|
|
2714
|
+
renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
|
|
2715
|
+
deriveRules: object.deriveRules ? object.deriveRules.map((rule) => ({ ...rule })) : void 0,
|
|
2716
|
+
validationRules: object.validationRules ? object.validationRules.map((rule) => ({ ...rule })) : void 0,
|
|
2717
|
+
lockedFields: object.lockedFields ? [...object.lockedFields] : void 0,
|
|
2718
|
+
tolerances: object.tolerances ? object.tolerances.map((entry) => ({
|
|
2719
|
+
field: entry.field,
|
|
2720
|
+
value: entry.value && typeof entry.value === "object" && "value" in entry.value ? { value: entry.value.value, unit: entry.value.unit } : Array.isArray(entry.value) ? [...entry.value] : entry.value
|
|
2721
|
+
})) : void 0,
|
|
2722
|
+
typedBlocks: object.typedBlocks ? Object.fromEntries(Object.entries(object.typedBlocks).map(([key, block]) => [key, { ...block ?? {} }])) : void 0,
|
|
2618
2723
|
properties: cloneProperties(object.properties),
|
|
2619
2724
|
placement: object.placement ? structuredClone(object.placement) : null,
|
|
2620
2725
|
info: { ...object.info }
|
|
@@ -2650,71 +2755,80 @@
|
|
|
2650
2755
|
if (system.defaults.units) {
|
|
2651
2756
|
properties.units = system.defaults.units;
|
|
2652
2757
|
}
|
|
2758
|
+
if (system.description) {
|
|
2759
|
+
properties.description = system.description;
|
|
2760
|
+
}
|
|
2761
|
+
if (system.epoch) {
|
|
2762
|
+
properties.epoch = system.epoch;
|
|
2763
|
+
}
|
|
2764
|
+
if (system.referencePlane) {
|
|
2765
|
+
properties.referencePlane = system.referencePlane;
|
|
2766
|
+
}
|
|
2653
2767
|
return properties;
|
|
2654
2768
|
}
|
|
2655
2769
|
function materializeDraftSystemInfo(system) {
|
|
2656
|
-
const
|
|
2770
|
+
const info2 = {
|
|
2657
2771
|
...system.atlasMetadata
|
|
2658
2772
|
};
|
|
2659
2773
|
if (system.defaults.theme) {
|
|
2660
|
-
|
|
2774
|
+
info2["atlas.theme"] = system.defaults.theme;
|
|
2661
2775
|
}
|
|
2662
2776
|
for (const viewpoint of system.viewpoints) {
|
|
2663
2777
|
const prefix = `viewpoint.${viewpoint.id}`;
|
|
2664
|
-
|
|
2778
|
+
info2[`${prefix}.label`] = viewpoint.label;
|
|
2665
2779
|
if (viewpoint.summary) {
|
|
2666
|
-
|
|
2780
|
+
info2[`${prefix}.summary`] = viewpoint.summary;
|
|
2667
2781
|
}
|
|
2668
2782
|
if (viewpoint.focusObjectId) {
|
|
2669
|
-
|
|
2783
|
+
info2[`${prefix}.focus`] = viewpoint.focusObjectId;
|
|
2670
2784
|
}
|
|
2671
2785
|
if (viewpoint.selectedObjectId) {
|
|
2672
|
-
|
|
2786
|
+
info2[`${prefix}.select`] = viewpoint.selectedObjectId;
|
|
2673
2787
|
}
|
|
2674
2788
|
if (viewpoint.projection) {
|
|
2675
|
-
|
|
2789
|
+
info2[`${prefix}.projection`] = viewpoint.projection;
|
|
2676
2790
|
}
|
|
2677
2791
|
if (viewpoint.preset) {
|
|
2678
|
-
|
|
2792
|
+
info2[`${prefix}.preset`] = viewpoint.preset;
|
|
2679
2793
|
}
|
|
2680
2794
|
if (viewpoint.zoom !== null) {
|
|
2681
|
-
|
|
2795
|
+
info2[`${prefix}.zoom`] = String(viewpoint.zoom);
|
|
2682
2796
|
}
|
|
2683
2797
|
if (viewpoint.rotationDeg !== 0) {
|
|
2684
|
-
|
|
2798
|
+
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2685
2799
|
}
|
|
2686
2800
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2687
2801
|
if (serializedLayers) {
|
|
2688
|
-
|
|
2802
|
+
info2[`${prefix}.layers`] = serializedLayers;
|
|
2689
2803
|
}
|
|
2690
2804
|
if (viewpoint.filter?.query) {
|
|
2691
|
-
|
|
2805
|
+
info2[`${prefix}.query`] = viewpoint.filter.query;
|
|
2692
2806
|
}
|
|
2693
2807
|
if ((viewpoint.filter?.objectTypes.length ?? 0) > 0) {
|
|
2694
|
-
|
|
2808
|
+
info2[`${prefix}.types`] = viewpoint.filter?.objectTypes.join(" ") ?? "";
|
|
2695
2809
|
}
|
|
2696
2810
|
if ((viewpoint.filter?.tags.length ?? 0) > 0) {
|
|
2697
|
-
|
|
2811
|
+
info2[`${prefix}.tags`] = viewpoint.filter?.tags.join(" ") ?? "";
|
|
2698
2812
|
}
|
|
2699
2813
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2700
|
-
|
|
2814
|
+
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2701
2815
|
}
|
|
2702
2816
|
}
|
|
2703
2817
|
for (const annotation of system.annotations) {
|
|
2704
2818
|
const prefix = `annotation.${annotation.id}`;
|
|
2705
|
-
|
|
2819
|
+
info2[`${prefix}.label`] = annotation.label;
|
|
2706
2820
|
if (annotation.targetObjectId) {
|
|
2707
|
-
|
|
2821
|
+
info2[`${prefix}.target`] = annotation.targetObjectId;
|
|
2708
2822
|
}
|
|
2709
|
-
|
|
2823
|
+
info2[`${prefix}.body`] = annotation.body;
|
|
2710
2824
|
if (annotation.tags.length > 0) {
|
|
2711
|
-
|
|
2825
|
+
info2[`${prefix}.tags`] = annotation.tags.join(" ");
|
|
2712
2826
|
}
|
|
2713
2827
|
if (annotation.sourceObjectId) {
|
|
2714
|
-
|
|
2828
|
+
info2[`${prefix}.source`] = annotation.sourceObjectId;
|
|
2715
2829
|
}
|
|
2716
2830
|
}
|
|
2717
|
-
return
|
|
2831
|
+
return info2;
|
|
2718
2832
|
}
|
|
2719
2833
|
function serializeViewpointLayers(layers) {
|
|
2720
2834
|
const tokens = [];
|
|
@@ -2723,7 +2837,7 @@
|
|
|
2723
2837
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2724
2838
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2725
2839
|
}
|
|
2726
|
-
for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
|
|
2840
|
+
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
2727
2841
|
if (layers[key] !== void 0) {
|
|
2728
2842
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2729
2843
|
}
|
|
@@ -2731,21 +2845,530 @@
|
|
|
2731
2845
|
return tokens.join(" ");
|
|
2732
2846
|
}
|
|
2733
2847
|
|
|
2848
|
+
// packages/core/dist/atlas-utils.js
|
|
2849
|
+
var UNIT_PATTERN2 = /^(-?\d+(?:\.\d+)?)(kpc|min|mj|rj|ky|my|gy|au|km|me|re|pc|ly|deg|sol|K|m|s|h|d|y)?$/;
|
|
2850
|
+
var BOOLEAN_VALUES2 = /* @__PURE__ */ new Map([
|
|
2851
|
+
["true", true],
|
|
2852
|
+
["false", false],
|
|
2853
|
+
["yes", true],
|
|
2854
|
+
["no", false]
|
|
2855
|
+
]);
|
|
2856
|
+
var URL_SCHEME_PATTERN2 = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
2857
|
+
function normalizeIdentifier(value) {
|
|
2858
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2859
|
+
}
|
|
2860
|
+
function humanizeIdentifier2(value) {
|
|
2861
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
2862
|
+
}
|
|
2863
|
+
function parseAtlasUnitValue(input, location, fieldKey) {
|
|
2864
|
+
const match = input.match(UNIT_PATTERN2);
|
|
2865
|
+
if (!match) {
|
|
2866
|
+
throw WorldOrbitError.fromLocation(`Invalid unit value "${input}"`, location);
|
|
2867
|
+
}
|
|
2868
|
+
const unitValue = {
|
|
2869
|
+
value: Number(match[1]),
|
|
2870
|
+
unit: match[2] ?? null
|
|
2871
|
+
};
|
|
2872
|
+
if (fieldKey) {
|
|
2873
|
+
const schema = getFieldSchema(fieldKey);
|
|
2874
|
+
if (schema?.unitFamily && !unitFamilyAllowsUnit(schema.unitFamily, unitValue.unit)) {
|
|
2875
|
+
throw WorldOrbitError.fromLocation(`Unit "${unitValue.unit ?? "none"}" is not valid for "${fieldKey}"`, location);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
return unitValue;
|
|
2879
|
+
}
|
|
2880
|
+
function tryParseAtlasUnitValue(input) {
|
|
2881
|
+
const match = input.match(UNIT_PATTERN2);
|
|
2882
|
+
if (!match) {
|
|
2883
|
+
return null;
|
|
2884
|
+
}
|
|
2885
|
+
return {
|
|
2886
|
+
value: Number(match[1]),
|
|
2887
|
+
unit: match[2] ?? null
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
function parseAtlasNumber(input, key, location) {
|
|
2891
|
+
const value = Number(input);
|
|
2892
|
+
if (!Number.isFinite(value)) {
|
|
2893
|
+
throw WorldOrbitError.fromLocation(`Invalid numeric value "${input}" for "${key}"`, location);
|
|
2894
|
+
}
|
|
2895
|
+
return value;
|
|
2896
|
+
}
|
|
2897
|
+
function parseAtlasBoolean(input, key, location) {
|
|
2898
|
+
const parsed = BOOLEAN_VALUES2.get(input.toLowerCase());
|
|
2899
|
+
if (parsed === void 0) {
|
|
2900
|
+
throw WorldOrbitError.fromLocation(`Invalid boolean value "${input}" for "${key}"`, location);
|
|
2901
|
+
}
|
|
2902
|
+
return parsed;
|
|
2903
|
+
}
|
|
2904
|
+
function parseAtlasAtReference(target, location) {
|
|
2905
|
+
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
2906
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
2907
|
+
}
|
|
2908
|
+
const pairedMatch = target.match(/^([A-Za-z0-9._-]+)-([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
2909
|
+
if (pairedMatch) {
|
|
2910
|
+
return {
|
|
2911
|
+
kind: "lagrange",
|
|
2912
|
+
primary: pairedMatch[1],
|
|
2913
|
+
secondary: pairedMatch[2],
|
|
2914
|
+
point: pairedMatch[3]
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
const simpleMatch = target.match(/^([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
2918
|
+
if (simpleMatch) {
|
|
2919
|
+
return {
|
|
2920
|
+
kind: "lagrange",
|
|
2921
|
+
primary: simpleMatch[1],
|
|
2922
|
+
secondary: null,
|
|
2923
|
+
point: simpleMatch[2]
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
if (/^[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
2927
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
2928
|
+
}
|
|
2929
|
+
const anchorMatch = target.match(/^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+)$/);
|
|
2930
|
+
if (anchorMatch) {
|
|
2931
|
+
return {
|
|
2932
|
+
kind: "anchor",
|
|
2933
|
+
objectId: anchorMatch[1],
|
|
2934
|
+
anchor: anchorMatch[2]
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
kind: "named",
|
|
2939
|
+
name: target
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function validateAtlasImageSource(value, location) {
|
|
2943
|
+
if (!value) {
|
|
2944
|
+
throw WorldOrbitError.fromLocation('Field "image" must not be empty', location);
|
|
2945
|
+
}
|
|
2946
|
+
if (value.startsWith("//")) {
|
|
2947
|
+
throw WorldOrbitError.fromLocation('Field "image" must use a relative path, root-relative path, or an http/https URL', location);
|
|
2948
|
+
}
|
|
2949
|
+
const schemeMatch = value.match(URL_SCHEME_PATTERN2);
|
|
2950
|
+
if (!schemeMatch) {
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const scheme = schemeMatch[0].slice(0, -1).toLowerCase();
|
|
2954
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
2955
|
+
throw WorldOrbitError.fromLocation(`Field "image" does not support the "${scheme}" scheme`, location);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
function normalizeLegacyScalarValue(key, values, location) {
|
|
2959
|
+
const schema = getFieldSchema(key);
|
|
2960
|
+
if (!schema) {
|
|
2961
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
2962
|
+
}
|
|
2963
|
+
if (schema.arity === "single" && values.length !== 1) {
|
|
2964
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
2965
|
+
}
|
|
2966
|
+
switch (schema.kind) {
|
|
2967
|
+
case "list":
|
|
2968
|
+
return values;
|
|
2969
|
+
case "boolean":
|
|
2970
|
+
return parseAtlasBoolean(singleAtlasValue(values, key, location), key, location);
|
|
2971
|
+
case "number":
|
|
2972
|
+
return parseAtlasNumber(singleAtlasValue(values, key, location), key, location);
|
|
2973
|
+
case "unit":
|
|
2974
|
+
return parseAtlasUnitValue(singleAtlasValue(values, key, location), location, key);
|
|
2975
|
+
case "string": {
|
|
2976
|
+
const value = values.join(" ").trim();
|
|
2977
|
+
if (key === "image") {
|
|
2978
|
+
validateAtlasImageSource(value, location);
|
|
2979
|
+
}
|
|
2980
|
+
return value;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function ensureAtlasFieldSupported(key, objectType, location) {
|
|
2985
|
+
const schema = getFieldSchema(key);
|
|
2986
|
+
if (!schema) {
|
|
2987
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
2988
|
+
}
|
|
2989
|
+
if (!schema.objectTypes.includes(objectType)) {
|
|
2990
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" is not valid on "${objectType}"`, location);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
function singleAtlasValue(values, key, location) {
|
|
2994
|
+
if (values.length !== 1) {
|
|
2995
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
2996
|
+
}
|
|
2997
|
+
return values[0];
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// packages/core/dist/atlas-validate.js
|
|
3001
|
+
var SURFACE_TARGET_TYPES2 = /* @__PURE__ */ new Set(["star", "planet", "moon", "asteroid", "comet"]);
|
|
3002
|
+
var EARTH_MASSES_PER_SOLAR = 332946.0487;
|
|
3003
|
+
var JUPITER_MASSES_PER_SOLAR = 1047.3486;
|
|
3004
|
+
var AU_IN_KM2 = 1495978707e-1;
|
|
3005
|
+
var EARTH_RADIUS_IN_KM2 = 6371;
|
|
3006
|
+
var SOLAR_RADIUS_IN_KM2 = 695700;
|
|
3007
|
+
var LY_IN_AU2 = 63241.077;
|
|
3008
|
+
var PC_IN_AU2 = 206264.806;
|
|
3009
|
+
var KPC_IN_AU2 = 206264806;
|
|
3010
|
+
function collectAtlasDiagnostics(document2, sourceSchemaVersion) {
|
|
3011
|
+
const diagnostics = [];
|
|
3012
|
+
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
3013
|
+
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3014
|
+
if (!document2.system) {
|
|
3015
|
+
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3016
|
+
}
|
|
3017
|
+
const knownIds = /* @__PURE__ */ new Map();
|
|
3018
|
+
for (const [kind, ids] of [
|
|
3019
|
+
["group", document2.groups.map((group) => group.id)],
|
|
3020
|
+
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3021
|
+
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3022
|
+
["relation", document2.relations.map((relation) => relation.id)],
|
|
3023
|
+
["object", document2.objects.map((object) => object.id)]
|
|
3024
|
+
]) {
|
|
3025
|
+
for (const id of ids) {
|
|
3026
|
+
const previous = knownIds.get(id);
|
|
3027
|
+
if (previous) {
|
|
3028
|
+
diagnostics.push(error("validate.id.duplicate", `Duplicate ${kind} id "${id}" already used by ${previous}.`));
|
|
3029
|
+
} else {
|
|
3030
|
+
knownIds.set(id, kind);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
for (const relation of document2.relations) {
|
|
3035
|
+
validateRelation(relation, objectMap, diagnostics);
|
|
3036
|
+
}
|
|
3037
|
+
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
3038
|
+
validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
|
|
3039
|
+
}
|
|
3040
|
+
for (const object of document2.objects) {
|
|
3041
|
+
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
3042
|
+
}
|
|
3043
|
+
return diagnostics;
|
|
3044
|
+
}
|
|
3045
|
+
function validateRelation(relation, objectMap, diagnostics) {
|
|
3046
|
+
if (!relation.from) {
|
|
3047
|
+
diagnostics.push(error("validate.relation.from.required", `Relation "${relation.id}" is missing a "from" target.`));
|
|
3048
|
+
} else if (!objectMap.has(relation.from)) {
|
|
3049
|
+
diagnostics.push(error("validate.relation.from.unknown", `Unknown relation source "${relation.from}" on "${relation.id}".`));
|
|
3050
|
+
}
|
|
3051
|
+
if (!relation.to) {
|
|
3052
|
+
diagnostics.push(error("validate.relation.to.required", `Relation "${relation.id}" is missing a "to" target.`));
|
|
3053
|
+
} else if (!objectMap.has(relation.to)) {
|
|
3054
|
+
diagnostics.push(error("validate.relation.to.unknown", `Unknown relation target "${relation.to}" on "${relation.id}".`));
|
|
3055
|
+
}
|
|
3056
|
+
if (!relation.kind) {
|
|
3057
|
+
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
function validateViewpointFilter(filter, groupIds, sourceSchemaVersion, diagnostics, viewpointId) {
|
|
3061
|
+
if (!filter || sourceSchemaVersion !== "2.1") {
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
for (const groupId of filter.groupIds) {
|
|
3065
|
+
if (!groupIds.has(groupId)) {
|
|
3066
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`));
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
3071
|
+
const placement = object.placement;
|
|
3072
|
+
const orbitPlacement = placement?.mode === "orbit" ? placement : null;
|
|
3073
|
+
const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
|
|
3074
|
+
if (object.groups) {
|
|
3075
|
+
for (const groupId of object.groups) {
|
|
3076
|
+
if (!groupIds.has(groupId)) {
|
|
3077
|
+
diagnostics.push(warn("validate.group.unknown", `Unknown group "${groupId}" on "${object.id}".`, object.id, "groups"));
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (orbitPlacement) {
|
|
3082
|
+
if (!objectMap.has(orbitPlacement.target)) {
|
|
3083
|
+
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
3084
|
+
}
|
|
3085
|
+
if (orbitPlacement.distance && orbitPlacement.semiMajor) {
|
|
3086
|
+
diagnostics.push(error("validate.orbit.distanceConflict", `Object "${object.id}" cannot declare both "distance" and "semiMajor".`, object.id, "distance"));
|
|
3087
|
+
}
|
|
3088
|
+
if (orbitPlacement.phase && !object.epoch && !system?.epoch) {
|
|
3089
|
+
diagnostics.push(warn("validate.phase.epochMissing", `Object "${object.id}" sets "phase" without an object or system epoch.`, object.id, "phase"));
|
|
3090
|
+
}
|
|
3091
|
+
if (orbitPlacement.inclination && !object.referencePlane && !system?.referencePlane) {
|
|
3092
|
+
diagnostics.push(warn("validate.inclination.referencePlaneMissing", `Object "${object.id}" sets "inclination" without an object or system reference plane.`, object.id, "inclination"));
|
|
3093
|
+
}
|
|
3094
|
+
if (orbitPlacement.period && !massInSolar(parentObject?.properties.mass)) {
|
|
3095
|
+
diagnostics.push(warn("validate.period.massMissing", `Object "${object.id}" sets "period" but its central mass cannot be derived.`, object.id, "period"));
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
if (placement?.mode === "surface") {
|
|
3099
|
+
const target = objectMap.get(placement.target);
|
|
3100
|
+
if (!target) {
|
|
3101
|
+
diagnostics.push(error("validate.surface.target.unknown", `Unknown placement target "${placement.target}" on "${object.id}".`, object.id, "surface"));
|
|
3102
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3103
|
+
diagnostics.push(error("validate.surface.target.invalid", `Surface target "${placement.target}" on "${object.id}" is not surface-capable.`, object.id, "surface"));
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (placement?.mode === "at") {
|
|
3107
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3108
|
+
diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
|
|
3109
|
+
}
|
|
3110
|
+
if (!validateAtTarget(object, objectMap, diagnostics)) {
|
|
3111
|
+
diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
if (object.resonance) {
|
|
3115
|
+
const target = objectMap.get(object.resonance.targetObjectId);
|
|
3116
|
+
if (!target) {
|
|
3117
|
+
diagnostics.push(error("validate.resonance.target.unknown", `Unknown resonance target "${object.resonance.targetObjectId}" on "${object.id}".`, object.id, "resonance"));
|
|
3118
|
+
} else if (object.placement?.mode !== "orbit" || target.placement?.mode !== "orbit" || object.placement.target !== target.placement.target) {
|
|
3119
|
+
diagnostics.push(warn("validate.resonance.orbitMismatch", `Resonance target "${object.resonance.targetObjectId}" on "${object.id}" does not share a compatible orbital parent.`, object.id, "resonance"));
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
for (const rule of object.deriveRules ?? []) {
|
|
3123
|
+
if (rule.field !== "period" || rule.strategy !== "kepler") {
|
|
3124
|
+
diagnostics.push(warn("validate.derive.unsupported", `Unsupported derive rule "${rule.field} ${rule.strategy}" on "${object.id}".`, object.id, "derive"));
|
|
3125
|
+
continue;
|
|
3126
|
+
}
|
|
3127
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
3128
|
+
if (derivedPeriodDays === null) {
|
|
3129
|
+
diagnostics.push(warn("validate.derive.inputsMissing", `Object "${object.id}" requests "derive period kepler" but lacks enough input data.`, object.id, "derive"));
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
if (!orbitPlacement?.period) {
|
|
3133
|
+
diagnostics.push(info("validate.derive.period.available", `Object "${object.id}" can derive a Kepler period of ${formatDays(derivedPeriodDays)}.`, object.id, "derive"));
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
for (const rule of object.validationRules ?? []) {
|
|
3137
|
+
if (rule.rule !== "kepler") {
|
|
3138
|
+
diagnostics.push(warn("validate.rule.unsupported", `Unsupported validation rule "${rule.rule}" on "${object.id}".`, object.id, "validate"));
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const actualPeriodDays = durationInDays(orbitPlacement?.period);
|
|
3142
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
3143
|
+
if (actualPeriodDays === null || derivedPeriodDays === null) {
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
const toleranceDays = toleranceForField(object, "period");
|
|
3147
|
+
if (Math.abs(actualPeriodDays - derivedPeriodDays) > toleranceDays) {
|
|
3148
|
+
diagnostics.push(error("validate.kepler.mismatch", `Object "${object.id}" fails Kepler validation for "period".`, object.id, "validate"));
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3153
|
+
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3154
|
+
if (!reference) {
|
|
3155
|
+
return true;
|
|
3156
|
+
}
|
|
3157
|
+
if (reference.kind === "named") {
|
|
3158
|
+
return objectMap.has(reference.name);
|
|
3159
|
+
}
|
|
3160
|
+
if (reference.kind === "anchor") {
|
|
3161
|
+
if (!objectMap.has(reference.objectId)) {
|
|
3162
|
+
diagnostics.push(error("validate.anchor.target.unknown", `Unknown anchor target "${reference.objectId}" on "${object.id}".`, object.id, "at"));
|
|
3163
|
+
return false;
|
|
3164
|
+
}
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
if (!objectMap.has(reference.primary)) {
|
|
3168
|
+
diagnostics.push(error("validate.lagrange.primary.unknown", `Unknown Lagrange reference "${reference.primary}" on "${object.id}".`, object.id, "at"));
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3172
|
+
diagnostics.push(error("validate.lagrange.secondary.unknown", `Unknown Lagrange reference "${reference.secondary}" on "${object.id}".`, object.id, "at"));
|
|
3173
|
+
return false;
|
|
3174
|
+
}
|
|
3175
|
+
return true;
|
|
3176
|
+
}
|
|
3177
|
+
function keplerPeriodDays(object, parentObject) {
|
|
3178
|
+
const placement = object.placement;
|
|
3179
|
+
if (!placement || placement.mode !== "orbit") {
|
|
3180
|
+
return null;
|
|
3181
|
+
}
|
|
3182
|
+
const semiMajorAu = distanceInAu(placement.semiMajor ?? placement.distance);
|
|
3183
|
+
const centralMassSolar = massInSolar(parentObject?.properties.mass);
|
|
3184
|
+
if (semiMajorAu === null || centralMassSolar === null || centralMassSolar <= 0) {
|
|
3185
|
+
return null;
|
|
3186
|
+
}
|
|
3187
|
+
const periodYears = Math.sqrt(semiMajorAu ** 3 / centralMassSolar);
|
|
3188
|
+
return periodYears * 365.25;
|
|
3189
|
+
}
|
|
3190
|
+
function distanceInAu(value) {
|
|
3191
|
+
if (!value)
|
|
3192
|
+
return null;
|
|
3193
|
+
switch (value.unit) {
|
|
3194
|
+
case null:
|
|
3195
|
+
case "au":
|
|
3196
|
+
return value.value;
|
|
3197
|
+
case "km":
|
|
3198
|
+
return value.value / AU_IN_KM2;
|
|
3199
|
+
case "m":
|
|
3200
|
+
return value.value / (AU_IN_KM2 * 1e3);
|
|
3201
|
+
case "ly":
|
|
3202
|
+
return value.value * LY_IN_AU2;
|
|
3203
|
+
case "pc":
|
|
3204
|
+
return value.value * PC_IN_AU2;
|
|
3205
|
+
case "kpc":
|
|
3206
|
+
return value.value * KPC_IN_AU2;
|
|
3207
|
+
case "re":
|
|
3208
|
+
return value.value * EARTH_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
3209
|
+
case "sol":
|
|
3210
|
+
return value.value * SOLAR_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
3211
|
+
default:
|
|
3212
|
+
return null;
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
function massInSolar(value) {
|
|
3216
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
const unitValue = value;
|
|
3220
|
+
switch (unitValue.unit) {
|
|
3221
|
+
case null:
|
|
3222
|
+
case "sol":
|
|
3223
|
+
return unitValue.value;
|
|
3224
|
+
case "me":
|
|
3225
|
+
return unitValue.value / EARTH_MASSES_PER_SOLAR;
|
|
3226
|
+
case "mj":
|
|
3227
|
+
return unitValue.value / JUPITER_MASSES_PER_SOLAR;
|
|
3228
|
+
default:
|
|
3229
|
+
return null;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
function durationInDays(value) {
|
|
3233
|
+
if (!value)
|
|
3234
|
+
return null;
|
|
3235
|
+
switch (value.unit) {
|
|
3236
|
+
case null:
|
|
3237
|
+
case "d":
|
|
3238
|
+
return value.value;
|
|
3239
|
+
case "s":
|
|
3240
|
+
return value.value / 86400;
|
|
3241
|
+
case "min":
|
|
3242
|
+
return value.value / 1440;
|
|
3243
|
+
case "h":
|
|
3244
|
+
return value.value / 24;
|
|
3245
|
+
case "y":
|
|
3246
|
+
return value.value * 365.25;
|
|
3247
|
+
case "ky":
|
|
3248
|
+
return value.value * 365250;
|
|
3249
|
+
case "my":
|
|
3250
|
+
return value.value * 36525e4;
|
|
3251
|
+
case "gy":
|
|
3252
|
+
return value.value * 36525e7;
|
|
3253
|
+
default:
|
|
3254
|
+
return null;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
function toleranceForField(object, field) {
|
|
3258
|
+
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
3259
|
+
if (typeof tolerance === "number") {
|
|
3260
|
+
return tolerance;
|
|
3261
|
+
}
|
|
3262
|
+
if (tolerance && typeof tolerance === "object" && "value" in tolerance) {
|
|
3263
|
+
return durationInDays(tolerance) ?? 0;
|
|
3264
|
+
}
|
|
3265
|
+
return 0;
|
|
3266
|
+
}
|
|
3267
|
+
function formatDays(days) {
|
|
3268
|
+
return `${Math.round(days * 100) / 100}d`;
|
|
3269
|
+
}
|
|
3270
|
+
function error(code, message, objectId, field) {
|
|
3271
|
+
return { code, severity: "error", source: "validate", message, objectId, field };
|
|
3272
|
+
}
|
|
3273
|
+
function warn(code, message, objectId, field) {
|
|
3274
|
+
return { code, severity: "warning", source: "validate", message, objectId, field };
|
|
3275
|
+
}
|
|
3276
|
+
function info(code, message, objectId, field) {
|
|
3277
|
+
return { code, severity: "info", source: "validate", message, objectId, field };
|
|
3278
|
+
}
|
|
3279
|
+
|
|
2734
3280
|
// packages/core/dist/draft-parse.js
|
|
3281
|
+
var STRUCTURED_TYPED_BLOCKS = /* @__PURE__ */ new Set([
|
|
3282
|
+
"climate",
|
|
3283
|
+
"habitability",
|
|
3284
|
+
"settlement"
|
|
3285
|
+
]);
|
|
3286
|
+
var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
|
|
3287
|
+
for (const key of [
|
|
3288
|
+
"orbit",
|
|
3289
|
+
"distance",
|
|
3290
|
+
"semiMajor",
|
|
3291
|
+
"eccentricity",
|
|
3292
|
+
"period",
|
|
3293
|
+
"angle",
|
|
3294
|
+
"inclination",
|
|
3295
|
+
"phase",
|
|
3296
|
+
"at",
|
|
3297
|
+
"surface",
|
|
3298
|
+
"free",
|
|
3299
|
+
"kind",
|
|
3300
|
+
"class",
|
|
3301
|
+
"culture",
|
|
3302
|
+
"tags",
|
|
3303
|
+
"color",
|
|
3304
|
+
"image",
|
|
3305
|
+
"hidden",
|
|
3306
|
+
"radius",
|
|
3307
|
+
"mass",
|
|
3308
|
+
"density",
|
|
3309
|
+
"gravity",
|
|
3310
|
+
"temperature",
|
|
3311
|
+
"albedo",
|
|
3312
|
+
"atmosphere",
|
|
3313
|
+
"inner",
|
|
3314
|
+
"outer",
|
|
3315
|
+
"on",
|
|
3316
|
+
"source",
|
|
3317
|
+
"cycle"
|
|
3318
|
+
]) {
|
|
3319
|
+
const schema = getFieldSchema(key);
|
|
3320
|
+
if (schema) {
|
|
3321
|
+
DRAFT_OBJECT_FIELD_SPECS.set(key, {
|
|
3322
|
+
key,
|
|
3323
|
+
version: "2.0",
|
|
3324
|
+
inlineMode: schema.arity === "multiple" ? "multiple" : "single",
|
|
3325
|
+
allowRepeat: false,
|
|
3326
|
+
legacySchema: schema
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
for (const spec of [
|
|
3331
|
+
{ key: "groups", inlineMode: "multiple", allowRepeat: false },
|
|
3332
|
+
{ key: "epoch", inlineMode: "single", allowRepeat: false },
|
|
3333
|
+
{ key: "referencePlane", inlineMode: "single", allowRepeat: false },
|
|
3334
|
+
{ key: "tidalLock", inlineMode: "single", allowRepeat: false },
|
|
3335
|
+
{ key: "renderLabel", inlineMode: "single", allowRepeat: false },
|
|
3336
|
+
{ key: "renderOrbit", inlineMode: "single", allowRepeat: false },
|
|
3337
|
+
{ key: "renderPriority", inlineMode: "single", allowRepeat: false },
|
|
3338
|
+
{ key: "resonance", inlineMode: "pair", allowRepeat: false },
|
|
3339
|
+
{ key: "derive", inlineMode: "pair", allowRepeat: true },
|
|
3340
|
+
{ key: "validate", inlineMode: "single", allowRepeat: true },
|
|
3341
|
+
{ key: "locked", inlineMode: "multiple", allowRepeat: false },
|
|
3342
|
+
{ key: "tolerance", inlineMode: "pair", allowRepeat: true }
|
|
3343
|
+
]) {
|
|
3344
|
+
DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
|
|
3345
|
+
key: spec.key,
|
|
3346
|
+
version: "2.1",
|
|
3347
|
+
inlineMode: spec.inlineMode,
|
|
3348
|
+
allowRepeat: spec.allowRepeat
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
2735
3352
|
function parseWorldOrbitAtlas(source) {
|
|
2736
|
-
return parseAtlasSource(source
|
|
3353
|
+
return parseAtlasSource(source);
|
|
2737
3354
|
}
|
|
2738
|
-
function parseAtlasSource(source,
|
|
2739
|
-
const
|
|
3355
|
+
function parseAtlasSource(source, forcedOutputVersion) {
|
|
3356
|
+
const prepared = preprocessAtlasSource(source);
|
|
3357
|
+
const lines = prepared.source.split(/\r?\n/);
|
|
3358
|
+
const diagnostics = [];
|
|
2740
3359
|
let sawSchemaHeader = false;
|
|
2741
|
-
let
|
|
3360
|
+
let sourceSchemaVersion = "2.0";
|
|
2742
3361
|
let system = null;
|
|
2743
3362
|
let section = null;
|
|
2744
3363
|
const objectNodes = [];
|
|
3364
|
+
const groups = [];
|
|
3365
|
+
const relations = [];
|
|
2745
3366
|
let sawDefaults = false;
|
|
2746
3367
|
let sawAtlas = false;
|
|
2747
3368
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
2748
3369
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3370
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
3371
|
+
const relationIds = /* @__PURE__ */ new Set();
|
|
2749
3372
|
for (let index = 0; index < lines.length; index++) {
|
|
2750
3373
|
const rawLine = lines[index];
|
|
2751
3374
|
const lineNumber = index + 1;
|
|
@@ -2761,15 +3384,22 @@
|
|
|
2761
3384
|
continue;
|
|
2762
3385
|
}
|
|
2763
3386
|
if (!sawSchemaHeader) {
|
|
2764
|
-
|
|
3387
|
+
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
2765
3388
|
sawSchemaHeader = true;
|
|
3389
|
+
if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
|
|
3390
|
+
diagnostics.push({
|
|
3391
|
+
code: "parse.schema21.commentCompatibility",
|
|
3392
|
+
severity: "warning",
|
|
3393
|
+
source: "parse",
|
|
3394
|
+
message: `Comments require schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
3395
|
+
line: prepared.comments[0].line,
|
|
3396
|
+
column: prepared.comments[0].column
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
2766
3399
|
continue;
|
|
2767
3400
|
}
|
|
2768
3401
|
if (indent === 0) {
|
|
2769
|
-
section = startTopLevelSection(tokens, lineNumber, system, objectNodes, viewpointIds, annotationIds, {
|
|
2770
|
-
sawDefaults,
|
|
2771
|
-
sawAtlas
|
|
2772
|
-
});
|
|
3402
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
2773
3403
|
if (section.kind === "system") {
|
|
2774
3404
|
system = section.system;
|
|
2775
3405
|
} else if (section.kind === "defaults") {
|
|
@@ -2787,48 +3417,57 @@
|
|
|
2787
3417
|
if (!sawSchemaHeader) {
|
|
2788
3418
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
2789
3419
|
}
|
|
2790
|
-
const
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
};
|
|
2794
|
-
const normalizedObjects = normalizeDocument(ast).objects;
|
|
2795
|
-
validateDocument({
|
|
2796
|
-
format: "worldorbit",
|
|
2797
|
-
version: "1.0",
|
|
2798
|
-
system: null,
|
|
2799
|
-
objects: normalizedObjects
|
|
2800
|
-
});
|
|
2801
|
-
const diagnostics = schemaVersion === "2.0-draft" && outputVersion === "2.0" ? [
|
|
2802
|
-
{
|
|
2803
|
-
code: "load.schema.deprecatedDraft",
|
|
2804
|
-
severity: "warning",
|
|
2805
|
-
source: "upgrade",
|
|
2806
|
-
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
2807
|
-
}
|
|
2808
|
-
] : [];
|
|
2809
|
-
return {
|
|
3420
|
+
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
3421
|
+
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3422
|
+
const baseDocument = {
|
|
2810
3423
|
format: "worldorbit",
|
|
2811
|
-
version: outputVersion,
|
|
2812
3424
|
sourceVersion: "1.0",
|
|
2813
3425
|
system,
|
|
2814
|
-
|
|
3426
|
+
groups,
|
|
3427
|
+
relations,
|
|
3428
|
+
objects,
|
|
2815
3429
|
diagnostics
|
|
2816
3430
|
};
|
|
3431
|
+
if (outputVersion === "2.0-draft") {
|
|
3432
|
+
const document3 = {
|
|
3433
|
+
...baseDocument,
|
|
3434
|
+
version: "2.0-draft",
|
|
3435
|
+
schemaVersion: "2.0-draft"
|
|
3436
|
+
};
|
|
3437
|
+
document3.diagnostics.push(...collectAtlasDiagnostics(document3, sourceSchemaVersion));
|
|
3438
|
+
return document3;
|
|
3439
|
+
}
|
|
3440
|
+
const document2 = {
|
|
3441
|
+
...baseDocument,
|
|
3442
|
+
version: outputVersion,
|
|
3443
|
+
schemaVersion: outputVersion
|
|
3444
|
+
};
|
|
3445
|
+
if (sourceSchemaVersion === "2.0-draft") {
|
|
3446
|
+
document2.diagnostics.push({
|
|
3447
|
+
code: "load.schema.deprecatedDraft",
|
|
3448
|
+
severity: "warning",
|
|
3449
|
+
source: "upgrade",
|
|
3450
|
+
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
document2.diagnostics.push(...collectAtlasDiagnostics(document2, sourceSchemaVersion));
|
|
3454
|
+
return document2;
|
|
2817
3455
|
}
|
|
2818
3456
|
function assertDraftSchemaHeader(tokens, line) {
|
|
2819
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" ||
|
|
2820
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0" or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3457
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3458
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
2821
3459
|
}
|
|
2822
|
-
|
|
3460
|
+
const version = tokens[1].value.toLowerCase();
|
|
3461
|
+
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
2823
3462
|
}
|
|
2824
|
-
function startTopLevelSection(tokens, line, system, objectNodes, viewpointIds, annotationIds, flags) {
|
|
3463
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
2825
3464
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
2826
3465
|
switch (keyword) {
|
|
2827
3466
|
case "system":
|
|
2828
3467
|
if (system) {
|
|
2829
3468
|
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
2830
3469
|
}
|
|
2831
|
-
return startSystemSection(tokens, line);
|
|
3470
|
+
return startSystemSection(tokens, line, sourceSchemaVersion, diagnostics);
|
|
2832
3471
|
case "defaults":
|
|
2833
3472
|
if (!system) {
|
|
2834
3473
|
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -2864,13 +3503,19 @@
|
|
|
2864
3503
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
2865
3504
|
}
|
|
2866
3505
|
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
3506
|
+
case "group":
|
|
3507
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "group", { line, column: tokens[0].column });
|
|
3508
|
+
return startGroupSection(tokens, line, groups, groupIds);
|
|
3509
|
+
case "relation":
|
|
3510
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3511
|
+
return startRelationSection(tokens, line, relations, relationIds);
|
|
2867
3512
|
case "object":
|
|
2868
|
-
return startObjectSection(tokens, line, objectNodes);
|
|
3513
|
+
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
2869
3514
|
default:
|
|
2870
3515
|
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
2871
3516
|
}
|
|
2872
3517
|
}
|
|
2873
|
-
function startSystemSection(tokens, line) {
|
|
3518
|
+
function startSystemSection(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
2874
3519
|
if (tokens.length !== 2) {
|
|
2875
3520
|
throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
|
|
2876
3521
|
}
|
|
@@ -2878,6 +3523,9 @@
|
|
|
2878
3523
|
type: "system",
|
|
2879
3524
|
id: tokens[1].value,
|
|
2880
3525
|
title: null,
|
|
3526
|
+
description: null,
|
|
3527
|
+
epoch: null,
|
|
3528
|
+
referencePlane: null,
|
|
2881
3529
|
defaults: {
|
|
2882
3530
|
view: "topdown",
|
|
2883
3531
|
scale: null,
|
|
@@ -2892,6 +3540,8 @@
|
|
|
2892
3540
|
return {
|
|
2893
3541
|
kind: "system",
|
|
2894
3542
|
system,
|
|
3543
|
+
sourceSchemaVersion,
|
|
3544
|
+
diagnostics,
|
|
2895
3545
|
seenFields: /* @__PURE__ */ new Set()
|
|
2896
3546
|
};
|
|
2897
3547
|
}
|
|
@@ -2957,7 +3607,64 @@
|
|
|
2957
3607
|
seenFields: /* @__PURE__ */ new Set()
|
|
2958
3608
|
};
|
|
2959
3609
|
}
|
|
2960
|
-
function
|
|
3610
|
+
function startGroupSection(tokens, line, groups, groupIds) {
|
|
3611
|
+
if (tokens.length !== 2) {
|
|
3612
|
+
throw new WorldOrbitError("Invalid group declaration", line, tokens[0]?.column ?? 1);
|
|
3613
|
+
}
|
|
3614
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3615
|
+
if (!id) {
|
|
3616
|
+
throw new WorldOrbitError("Group id must not be empty", line, tokens[1].column);
|
|
3617
|
+
}
|
|
3618
|
+
if (groupIds.has(id)) {
|
|
3619
|
+
throw new WorldOrbitError(`Duplicate group id "${id}"`, line, tokens[1].column);
|
|
3620
|
+
}
|
|
3621
|
+
const group = {
|
|
3622
|
+
id,
|
|
3623
|
+
label: humanizeIdentifier2(id),
|
|
3624
|
+
summary: "",
|
|
3625
|
+
color: null,
|
|
3626
|
+
tags: [],
|
|
3627
|
+
hidden: false
|
|
3628
|
+
};
|
|
3629
|
+
groups.push(group);
|
|
3630
|
+
groupIds.add(id);
|
|
3631
|
+
return {
|
|
3632
|
+
kind: "group",
|
|
3633
|
+
group,
|
|
3634
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
function startRelationSection(tokens, line, relations, relationIds) {
|
|
3638
|
+
if (tokens.length !== 2) {
|
|
3639
|
+
throw new WorldOrbitError("Invalid relation declaration", line, tokens[0]?.column ?? 1);
|
|
3640
|
+
}
|
|
3641
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3642
|
+
if (!id) {
|
|
3643
|
+
throw new WorldOrbitError("Relation id must not be empty", line, tokens[1].column);
|
|
3644
|
+
}
|
|
3645
|
+
if (relationIds.has(id)) {
|
|
3646
|
+
throw new WorldOrbitError(`Duplicate relation id "${id}"`, line, tokens[1].column);
|
|
3647
|
+
}
|
|
3648
|
+
const relation = {
|
|
3649
|
+
id,
|
|
3650
|
+
from: "",
|
|
3651
|
+
to: "",
|
|
3652
|
+
kind: "",
|
|
3653
|
+
label: null,
|
|
3654
|
+
summary: null,
|
|
3655
|
+
tags: [],
|
|
3656
|
+
color: null,
|
|
3657
|
+
hidden: false
|
|
3658
|
+
};
|
|
3659
|
+
relations.push(relation);
|
|
3660
|
+
relationIds.add(id);
|
|
3661
|
+
return {
|
|
3662
|
+
kind: "relation",
|
|
3663
|
+
relation,
|
|
3664
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
2961
3668
|
if (tokens.length < 3) {
|
|
2962
3669
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
2963
3670
|
}
|
|
@@ -2968,12 +3675,11 @@
|
|
|
2968
3675
|
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
2969
3676
|
}
|
|
2970
3677
|
const objectNode = {
|
|
2971
|
-
type: "object",
|
|
2972
3678
|
objectType,
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
blockFields: [],
|
|
3679
|
+
id: idToken.value,
|
|
3680
|
+
fields: parseInlineObjectFields(tokens.slice(3), line, objectType, sourceSchemaVersion, diagnostics),
|
|
2976
3681
|
infoEntries: [],
|
|
3682
|
+
typedBlockEntries: {},
|
|
2977
3683
|
location: {
|
|
2978
3684
|
line,
|
|
2979
3685
|
column: objectTypeToken.column
|
|
@@ -2983,8 +3689,12 @@
|
|
|
2983
3689
|
return {
|
|
2984
3690
|
kind: "object",
|
|
2985
3691
|
objectNode,
|
|
2986
|
-
|
|
2987
|
-
|
|
3692
|
+
sourceSchemaVersion,
|
|
3693
|
+
diagnostics,
|
|
3694
|
+
activeBlock: null,
|
|
3695
|
+
blockIndent: null,
|
|
3696
|
+
seenInfoKeys: /* @__PURE__ */ new Set(),
|
|
3697
|
+
seenTypedBlockKeys: {}
|
|
2988
3698
|
};
|
|
2989
3699
|
}
|
|
2990
3700
|
function handleSectionLine(section, indent, tokens, line) {
|
|
@@ -3004,6 +3714,12 @@
|
|
|
3004
3714
|
case "annotation":
|
|
3005
3715
|
applyAnnotationField(section, tokens, line);
|
|
3006
3716
|
return;
|
|
3717
|
+
case "group":
|
|
3718
|
+
applyGroupField(section, tokens, line);
|
|
3719
|
+
return;
|
|
3720
|
+
case "relation":
|
|
3721
|
+
applyRelationField(section, tokens, line);
|
|
3722
|
+
return;
|
|
3007
3723
|
case "object":
|
|
3008
3724
|
applyObjectField(section, indent, tokens, line);
|
|
3009
3725
|
return;
|
|
@@ -3011,10 +3727,35 @@
|
|
|
3011
3727
|
}
|
|
3012
3728
|
function applySystemField(section, tokens, line) {
|
|
3013
3729
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3014
|
-
|
|
3015
|
-
|
|
3730
|
+
const value = joinFieldValue(tokens, line);
|
|
3731
|
+
switch (key) {
|
|
3732
|
+
case "title":
|
|
3733
|
+
section.system.title = value;
|
|
3734
|
+
return;
|
|
3735
|
+
case "description":
|
|
3736
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
3737
|
+
line,
|
|
3738
|
+
column: tokens[0].column
|
|
3739
|
+
});
|
|
3740
|
+
section.system.description = value;
|
|
3741
|
+
return;
|
|
3742
|
+
case "epoch":
|
|
3743
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
3744
|
+
line,
|
|
3745
|
+
column: tokens[0].column
|
|
3746
|
+
});
|
|
3747
|
+
section.system.epoch = value;
|
|
3748
|
+
return;
|
|
3749
|
+
case "referenceplane":
|
|
3750
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "referencePlane", {
|
|
3751
|
+
line,
|
|
3752
|
+
column: tokens[0].column
|
|
3753
|
+
});
|
|
3754
|
+
section.system.referencePlane = value;
|
|
3755
|
+
return;
|
|
3756
|
+
default:
|
|
3757
|
+
throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3016
3758
|
}
|
|
3017
|
-
section.system.title = joinFieldValue(tokens, line);
|
|
3018
3759
|
}
|
|
3019
3760
|
function applyDefaultsField(section, tokens, line) {
|
|
3020
3761
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
@@ -3045,14 +3786,11 @@
|
|
|
3045
3786
|
section.metadataIndent = null;
|
|
3046
3787
|
}
|
|
3047
3788
|
if (section.inMetadata) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3789
|
+
const entry = parseInfoLikeEntry(tokens, line, "Invalid atlas metadata entry");
|
|
3790
|
+
if (entry.key in section.system.atlasMetadata) {
|
|
3791
|
+
throw new WorldOrbitError(`Duplicate atlas metadata key "${entry.key}"`, line, tokens[0].column);
|
|
3050
3792
|
}
|
|
3051
|
-
|
|
3052
|
-
if (key in section.system.atlasMetadata) {
|
|
3053
|
-
throw new WorldOrbitError(`Duplicate atlas metadata key "${key}"`, line, tokens[0].column);
|
|
3054
|
-
}
|
|
3055
|
-
section.system.atlasMetadata[key] = joinFieldValue(tokens, line);
|
|
3793
|
+
section.system.atlasMetadata[entry.key] = entry.value;
|
|
3056
3794
|
return;
|
|
3057
3795
|
}
|
|
3058
3796
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
|
|
@@ -3131,44 +3869,125 @@
|
|
|
3131
3869
|
filter.groupIds = parseTokenList(tokens.slice(1), line, "groups");
|
|
3132
3870
|
break;
|
|
3133
3871
|
default:
|
|
3134
|
-
throw new WorldOrbitError(`Unknown viewpoint filter field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3872
|
+
throw new WorldOrbitError(`Unknown viewpoint filter field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3873
|
+
}
|
|
3874
|
+
section.viewpoint.filter = filter;
|
|
3875
|
+
}
|
|
3876
|
+
function applyAnnotationField(section, tokens, line) {
|
|
3877
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3878
|
+
switch (key) {
|
|
3879
|
+
case "label":
|
|
3880
|
+
section.annotation.label = joinFieldValue(tokens, line);
|
|
3881
|
+
return;
|
|
3882
|
+
case "target":
|
|
3883
|
+
section.annotation.targetObjectId = joinFieldValue(tokens, line);
|
|
3884
|
+
return;
|
|
3885
|
+
case "body":
|
|
3886
|
+
section.annotation.body = joinFieldValue(tokens, line);
|
|
3887
|
+
return;
|
|
3888
|
+
case "tags":
|
|
3889
|
+
section.annotation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3890
|
+
return;
|
|
3891
|
+
default:
|
|
3892
|
+
throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
function applyGroupField(section, tokens, line) {
|
|
3896
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3897
|
+
switch (key) {
|
|
3898
|
+
case "label":
|
|
3899
|
+
section.group.label = joinFieldValue(tokens, line);
|
|
3900
|
+
return;
|
|
3901
|
+
case "summary":
|
|
3902
|
+
section.group.summary = joinFieldValue(tokens, line);
|
|
3903
|
+
return;
|
|
3904
|
+
case "color":
|
|
3905
|
+
section.group.color = joinFieldValue(tokens, line);
|
|
3906
|
+
return;
|
|
3907
|
+
case "tags":
|
|
3908
|
+
section.group.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3909
|
+
return;
|
|
3910
|
+
case "hidden":
|
|
3911
|
+
section.group.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
3912
|
+
line,
|
|
3913
|
+
column: tokens[0].column
|
|
3914
|
+
});
|
|
3915
|
+
return;
|
|
3916
|
+
default:
|
|
3917
|
+
throw new WorldOrbitError(`Unknown group field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3135
3918
|
}
|
|
3136
|
-
section.viewpoint.filter = filter;
|
|
3137
3919
|
}
|
|
3138
|
-
function
|
|
3920
|
+
function applyRelationField(section, tokens, line) {
|
|
3139
3921
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3140
3922
|
switch (key) {
|
|
3141
|
-
case "
|
|
3142
|
-
section.
|
|
3923
|
+
case "from":
|
|
3924
|
+
section.relation.from = joinFieldValue(tokens, line);
|
|
3143
3925
|
return;
|
|
3144
|
-
case "
|
|
3145
|
-
section.
|
|
3926
|
+
case "to":
|
|
3927
|
+
section.relation.to = joinFieldValue(tokens, line);
|
|
3146
3928
|
return;
|
|
3147
|
-
case "
|
|
3148
|
-
section.
|
|
3929
|
+
case "kind":
|
|
3930
|
+
section.relation.kind = joinFieldValue(tokens, line);
|
|
3931
|
+
return;
|
|
3932
|
+
case "label":
|
|
3933
|
+
section.relation.label = joinFieldValue(tokens, line);
|
|
3934
|
+
return;
|
|
3935
|
+
case "summary":
|
|
3936
|
+
section.relation.summary = joinFieldValue(tokens, line);
|
|
3149
3937
|
return;
|
|
3150
3938
|
case "tags":
|
|
3151
|
-
section.
|
|
3939
|
+
section.relation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3940
|
+
return;
|
|
3941
|
+
case "color":
|
|
3942
|
+
section.relation.color = joinFieldValue(tokens, line);
|
|
3943
|
+
return;
|
|
3944
|
+
case "hidden":
|
|
3945
|
+
section.relation.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
3946
|
+
line,
|
|
3947
|
+
column: tokens[0].column
|
|
3948
|
+
});
|
|
3152
3949
|
return;
|
|
3153
3950
|
default:
|
|
3154
|
-
throw new WorldOrbitError(`Unknown
|
|
3951
|
+
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3155
3952
|
}
|
|
3156
3953
|
}
|
|
3157
3954
|
function applyObjectField(section, indent, tokens, line) {
|
|
3158
|
-
if (
|
|
3159
|
-
section.
|
|
3160
|
-
section.
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3955
|
+
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
3956
|
+
section.activeBlock = null;
|
|
3957
|
+
section.blockIndent = null;
|
|
3958
|
+
}
|
|
3959
|
+
if (tokens.length === 1) {
|
|
3960
|
+
const blockName = tokens[0].value.toLowerCase();
|
|
3961
|
+
if (blockName === "info" || STRUCTURED_TYPED_BLOCKS.has(blockName)) {
|
|
3962
|
+
if (blockName !== "info") {
|
|
3963
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, blockName, { line, column: tokens[0].column });
|
|
3964
|
+
}
|
|
3965
|
+
section.activeBlock = blockName;
|
|
3966
|
+
section.blockIndent = indent;
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3166
3969
|
}
|
|
3167
|
-
if (section.
|
|
3168
|
-
|
|
3970
|
+
if (section.activeBlock) {
|
|
3971
|
+
const entry = parseInfoLikeEntry(tokens, line, `Invalid ${section.activeBlock} entry`);
|
|
3972
|
+
if (section.activeBlock === "info") {
|
|
3973
|
+
if (section.seenInfoKeys.has(entry.key)) {
|
|
3974
|
+
throw new WorldOrbitError(`Duplicate info key "${entry.key}"`, line, tokens[0].column);
|
|
3975
|
+
}
|
|
3976
|
+
section.seenInfoKeys.add(entry.key);
|
|
3977
|
+
section.objectNode.infoEntries.push(entry);
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
const typedBlock = section.activeBlock;
|
|
3981
|
+
const seenKeys = section.seenTypedBlockKeys[typedBlock] ?? (section.seenTypedBlockKeys[typedBlock] = /* @__PURE__ */ new Set());
|
|
3982
|
+
if (seenKeys.has(entry.key)) {
|
|
3983
|
+
throw new WorldOrbitError(`Duplicate ${typedBlock} key "${entry.key}"`, line, tokens[0].column);
|
|
3984
|
+
}
|
|
3985
|
+
seenKeys.add(entry.key);
|
|
3986
|
+
const entries = section.objectNode.typedBlockEntries[typedBlock] ?? (section.objectNode.typedBlockEntries[typedBlock] = []);
|
|
3987
|
+
entries.push(entry);
|
|
3169
3988
|
return;
|
|
3170
3989
|
}
|
|
3171
|
-
section.objectNode.
|
|
3990
|
+
section.objectNode.fields.push(parseObjectField(tokens, line, section.objectNode.objectType, section.sourceSchemaVersion, section.diagnostics));
|
|
3172
3991
|
}
|
|
3173
3992
|
function requireUniqueField(tokens, seenFields, line) {
|
|
3174
3993
|
if (tokens.length < 2) {
|
|
@@ -3188,50 +4007,40 @@
|
|
|
3188
4007
|
return tokens.slice(1).map((token) => token.value).join(" ").trim();
|
|
3189
4008
|
}
|
|
3190
4009
|
function parseObjectTypeTokens(tokens, line) {
|
|
3191
|
-
|
|
3192
|
-
throw new WorldOrbitError("Missing value for atlas field", line);
|
|
3193
|
-
}
|
|
3194
|
-
return tokens.map((token) => {
|
|
3195
|
-
const value = token.value;
|
|
3196
|
-
if (value !== "star" && value !== "planet" && value !== "moon" && value !== "belt" && value !== "asteroid" && value !== "comet" && value !== "ring" && value !== "structure" && value !== "phenomenon") {
|
|
3197
|
-
throw new WorldOrbitError(`Unknown viewpoint object type "${token.value}"`, line, token.column);
|
|
3198
|
-
}
|
|
3199
|
-
return value;
|
|
3200
|
-
});
|
|
3201
|
-
}
|
|
3202
|
-
function parseTokenList(tokens, line, field) {
|
|
3203
|
-
if (tokens.length === 0) {
|
|
3204
|
-
throw new WorldOrbitError(`Missing value for field "${field}"`, line);
|
|
3205
|
-
}
|
|
3206
|
-
return tokens.map((token) => token.value);
|
|
4010
|
+
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");
|
|
3207
4011
|
}
|
|
3208
4012
|
function parseLayerTokens(tokens, line) {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
if (rawLayer === "orbits") {
|
|
3217
|
-
next["orbits-back"] = enabled;
|
|
3218
|
-
next["orbits-front"] = enabled;
|
|
4013
|
+
const layers = {};
|
|
4014
|
+
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4015
|
+
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
4016
|
+
const raw = token.replace(/^[-!]+/, "").toLowerCase();
|
|
4017
|
+
if (raw === "orbits") {
|
|
4018
|
+
layers["orbits-back"] = enabled;
|
|
4019
|
+
layers["orbits-front"] = enabled;
|
|
3219
4020
|
continue;
|
|
3220
4021
|
}
|
|
3221
|
-
if (
|
|
3222
|
-
|
|
3223
|
-
continue;
|
|
4022
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4023
|
+
layers[raw] = enabled;
|
|
3224
4024
|
}
|
|
3225
|
-
throw new WorldOrbitError(`Unknown layer token "${token.value}"`, line, token.column);
|
|
3226
4025
|
}
|
|
3227
|
-
return
|
|
4026
|
+
return layers;
|
|
4027
|
+
}
|
|
4028
|
+
function parseTokenList(tokens, line, fieldName) {
|
|
4029
|
+
if (tokens.length === 0) {
|
|
4030
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, 1);
|
|
4031
|
+
}
|
|
4032
|
+
const values = tokens.map((token) => token.value).filter(Boolean);
|
|
4033
|
+
if (values.length === 0) {
|
|
4034
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, tokens[0]?.column ?? 1);
|
|
4035
|
+
}
|
|
4036
|
+
return values;
|
|
3228
4037
|
}
|
|
3229
4038
|
function parseProjectionValue(value, line, column) {
|
|
3230
4039
|
const normalized = value.toLowerCase();
|
|
3231
|
-
if (normalized
|
|
3232
|
-
|
|
4040
|
+
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
4041
|
+
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
3233
4042
|
}
|
|
3234
|
-
|
|
4043
|
+
return normalized;
|
|
3235
4044
|
}
|
|
3236
4045
|
function parsePresetValue(value, line, column) {
|
|
3237
4046
|
const normalized = value.toLowerCase();
|
|
@@ -3241,16 +4050,16 @@
|
|
|
3241
4050
|
throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
|
|
3242
4051
|
}
|
|
3243
4052
|
function parsePositiveNumber2(value, line, column, field) {
|
|
3244
|
-
const parsed =
|
|
3245
|
-
if (
|
|
3246
|
-
throw new WorldOrbitError(`Field "${field}"
|
|
4053
|
+
const parsed = parseFiniteNumber2(value, line, column, field);
|
|
4054
|
+
if (parsed <= 0) {
|
|
4055
|
+
throw new WorldOrbitError(`Field "${field}" must be greater than zero`, line, column);
|
|
3247
4056
|
}
|
|
3248
4057
|
return parsed;
|
|
3249
4058
|
}
|
|
3250
4059
|
function parseFiniteNumber2(value, line, column, field) {
|
|
3251
4060
|
const parsed = Number(value);
|
|
3252
4061
|
if (!Number.isFinite(parsed)) {
|
|
3253
|
-
throw new WorldOrbitError(`
|
|
4062
|
+
throw new WorldOrbitError(`Invalid numeric value "${value}" for "${field}"`, line, column);
|
|
3254
4063
|
}
|
|
3255
4064
|
return parsed;
|
|
3256
4065
|
}
|
|
@@ -3262,28 +4071,43 @@
|
|
|
3262
4071
|
groupIds: []
|
|
3263
4072
|
};
|
|
3264
4073
|
}
|
|
3265
|
-
function
|
|
4074
|
+
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
3266
4075
|
const fields = [];
|
|
3267
4076
|
let index = 0;
|
|
3268
4077
|
while (index < tokens.length) {
|
|
3269
4078
|
const keyToken = tokens[index];
|
|
3270
|
-
const
|
|
3271
|
-
if (!
|
|
4079
|
+
const spec = getDraftObjectFieldSpec(keyToken.value);
|
|
4080
|
+
if (!spec) {
|
|
3272
4081
|
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
3273
4082
|
}
|
|
4083
|
+
if (spec.version === "2.1") {
|
|
4084
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
|
|
4085
|
+
line,
|
|
4086
|
+
column: keyToken.column
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
3274
4089
|
index++;
|
|
3275
4090
|
const valueTokens = [];
|
|
3276
|
-
if (
|
|
3277
|
-
while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
|
|
3278
|
-
valueTokens.push(tokens[index]);
|
|
3279
|
-
index++;
|
|
3280
|
-
}
|
|
3281
|
-
} else {
|
|
4091
|
+
if (spec.inlineMode === "single") {
|
|
3282
4092
|
const nextToken = tokens[index];
|
|
3283
4093
|
if (nextToken) {
|
|
3284
4094
|
valueTokens.push(nextToken);
|
|
3285
4095
|
index++;
|
|
3286
4096
|
}
|
|
4097
|
+
} else if (spec.inlineMode === "pair") {
|
|
4098
|
+
for (let count = 0; count < 2; count++) {
|
|
4099
|
+
const nextToken = tokens[index];
|
|
4100
|
+
if (!nextToken) {
|
|
4101
|
+
break;
|
|
4102
|
+
}
|
|
4103
|
+
valueTokens.push(nextToken);
|
|
4104
|
+
index++;
|
|
4105
|
+
}
|
|
4106
|
+
} else {
|
|
4107
|
+
while (index < tokens.length && !DRAFT_OBJECT_FIELD_KEYS.has(tokens[index].value)) {
|
|
4108
|
+
valueTokens.push(tokens[index]);
|
|
4109
|
+
index++;
|
|
4110
|
+
}
|
|
3287
4111
|
}
|
|
3288
4112
|
if (valueTokens.length === 0) {
|
|
3289
4113
|
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
@@ -3295,25 +4119,35 @@
|
|
|
3295
4119
|
location: { line, column: keyToken.column }
|
|
3296
4120
|
});
|
|
3297
4121
|
}
|
|
4122
|
+
validateDraftObjectFieldCompatibility(fields, objectType);
|
|
3298
4123
|
return fields;
|
|
3299
4124
|
}
|
|
3300
|
-
function
|
|
4125
|
+
function parseObjectField(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
3301
4126
|
if (tokens.length < 2) {
|
|
3302
4127
|
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
3303
4128
|
}
|
|
3304
|
-
|
|
4129
|
+
const spec = getDraftObjectFieldSpec(tokens[0].value);
|
|
4130
|
+
if (!spec) {
|
|
3305
4131
|
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3306
4132
|
}
|
|
3307
|
-
|
|
4133
|
+
if (spec.version === "2.1") {
|
|
4134
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
|
|
4135
|
+
line,
|
|
4136
|
+
column: tokens[0].column
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
const field = {
|
|
3308
4140
|
type: "field",
|
|
3309
4141
|
key: tokens[0].value,
|
|
3310
4142
|
values: tokens.slice(1).map((token) => token.value),
|
|
3311
4143
|
location: { line, column: tokens[0].column }
|
|
3312
4144
|
};
|
|
4145
|
+
validateDraftObjectFieldCompatibility([field], objectType);
|
|
4146
|
+
return field;
|
|
3313
4147
|
}
|
|
3314
|
-
function
|
|
4148
|
+
function parseInfoLikeEntry(tokens, line, errorMessage) {
|
|
3315
4149
|
if (tokens.length < 2) {
|
|
3316
|
-
throw new WorldOrbitError(
|
|
4150
|
+
throw new WorldOrbitError(errorMessage, line, tokens[0]?.column ?? 1);
|
|
3317
4151
|
}
|
|
3318
4152
|
return {
|
|
3319
4153
|
type: "info-entry",
|
|
@@ -3322,18 +4156,348 @@
|
|
|
3322
4156
|
location: { line, column: tokens[0].column }
|
|
3323
4157
|
};
|
|
3324
4158
|
}
|
|
3325
|
-
function
|
|
3326
|
-
|
|
4159
|
+
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4160
|
+
const fieldMap = collectDraftFields(node.fields);
|
|
4161
|
+
const placement = extractDraftPlacement(node.objectType, fieldMap);
|
|
4162
|
+
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4163
|
+
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4164
|
+
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
4165
|
+
const referencePlane = parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0]);
|
|
4166
|
+
const tidalLock = fieldMap.has("tidalLock") ? parseAtlasBoolean(singleFieldValue2(fieldMap.get("tidalLock")[0]), "tidalLock", fieldMap.get("tidalLock")[0].location) : void 0;
|
|
4167
|
+
const resonance = fieldMap.has("resonance") ? parseResonanceField(fieldMap.get("resonance")[0]) : void 0;
|
|
4168
|
+
const renderHints = extractRenderHints(fieldMap);
|
|
4169
|
+
const deriveRules = fieldMap.get("derive")?.map((field) => parseDeriveField(field));
|
|
4170
|
+
const validationRules = fieldMap.get("validate")?.map((field) => ({
|
|
4171
|
+
rule: singleFieldValue2(field)
|
|
4172
|
+
}));
|
|
4173
|
+
const lockedFields = fieldMap.has("locked") ? [...new Set(fieldMap.get("locked").flatMap((field) => field.values))] : void 0;
|
|
4174
|
+
const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
|
|
4175
|
+
const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
|
|
4176
|
+
const info2 = normalizeInfoEntries(node.infoEntries, "info");
|
|
4177
|
+
const object = {
|
|
4178
|
+
type: node.objectType,
|
|
4179
|
+
id: node.id,
|
|
4180
|
+
properties,
|
|
4181
|
+
placement,
|
|
4182
|
+
info: info2
|
|
4183
|
+
};
|
|
4184
|
+
if (groups.length > 0)
|
|
4185
|
+
object.groups = groups;
|
|
4186
|
+
if (epoch)
|
|
4187
|
+
object.epoch = epoch;
|
|
4188
|
+
if (referencePlane)
|
|
4189
|
+
object.referencePlane = referencePlane;
|
|
4190
|
+
if (tidalLock !== void 0)
|
|
4191
|
+
object.tidalLock = tidalLock;
|
|
4192
|
+
if (resonance)
|
|
4193
|
+
object.resonance = resonance;
|
|
4194
|
+
if (renderHints)
|
|
4195
|
+
object.renderHints = renderHints;
|
|
4196
|
+
if (deriveRules?.length)
|
|
4197
|
+
object.deriveRules = deriveRules;
|
|
4198
|
+
if (validationRules?.length)
|
|
4199
|
+
object.validationRules = validationRules;
|
|
4200
|
+
if (lockedFields?.length)
|
|
4201
|
+
object.lockedFields = lockedFields;
|
|
4202
|
+
if (tolerances?.length)
|
|
4203
|
+
object.tolerances = tolerances;
|
|
4204
|
+
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
4205
|
+
object.typedBlocks = typedBlocks;
|
|
4206
|
+
if (sourceSchemaVersion !== "2.1") {
|
|
4207
|
+
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) {
|
|
4208
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
return object;
|
|
4212
|
+
}
|
|
4213
|
+
function collectDraftFields(fields) {
|
|
4214
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4215
|
+
for (const field of fields) {
|
|
4216
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
4217
|
+
if (!spec) {
|
|
4218
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4219
|
+
}
|
|
4220
|
+
if (!spec.allowRepeat && grouped.has(field.key)) {
|
|
4221
|
+
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
4222
|
+
}
|
|
4223
|
+
const existing = grouped.get(field.key) ?? [];
|
|
4224
|
+
existing.push(field);
|
|
4225
|
+
grouped.set(field.key, existing);
|
|
4226
|
+
}
|
|
4227
|
+
return grouped;
|
|
3327
4228
|
}
|
|
3328
|
-
function
|
|
3329
|
-
|
|
4229
|
+
function extractDraftPlacement(objectType, fieldMap) {
|
|
4230
|
+
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4231
|
+
const atField = fieldMap.get("at")?.[0];
|
|
4232
|
+
const surfaceField = fieldMap.get("surface")?.[0];
|
|
4233
|
+
const freeField = fieldMap.get("free")?.[0];
|
|
4234
|
+
const count = [orbitField, atField, surfaceField, freeField].filter(Boolean).length;
|
|
4235
|
+
if (count > 1) {
|
|
4236
|
+
const conflictingField = orbitField ?? atField ?? surfaceField ?? freeField;
|
|
4237
|
+
throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
|
|
4238
|
+
}
|
|
4239
|
+
if (orbitField) {
|
|
4240
|
+
return {
|
|
4241
|
+
mode: "orbit",
|
|
4242
|
+
target: singleFieldValue2(orbitField),
|
|
4243
|
+
distance: parseOptionalUnitField(fieldMap.get("distance")?.[0], "distance"),
|
|
4244
|
+
semiMajor: parseOptionalUnitField(fieldMap.get("semiMajor")?.[0], "semiMajor"),
|
|
4245
|
+
eccentricity: parseOptionalNumberField(fieldMap.get("eccentricity")?.[0], "eccentricity"),
|
|
4246
|
+
period: parseOptionalUnitField(fieldMap.get("period")?.[0], "period"),
|
|
4247
|
+
angle: parseOptionalUnitField(fieldMap.get("angle")?.[0], "angle"),
|
|
4248
|
+
inclination: parseOptionalUnitField(fieldMap.get("inclination")?.[0], "inclination"),
|
|
4249
|
+
phase: parseOptionalUnitField(fieldMap.get("phase")?.[0], "phase")
|
|
4250
|
+
};
|
|
4251
|
+
}
|
|
4252
|
+
if (atField) {
|
|
4253
|
+
const target = singleFieldValue2(atField);
|
|
4254
|
+
return {
|
|
4255
|
+
mode: "at",
|
|
4256
|
+
target,
|
|
4257
|
+
reference: parseAtlasAtReference(target, atField.location)
|
|
4258
|
+
};
|
|
4259
|
+
}
|
|
4260
|
+
if (surfaceField) {
|
|
4261
|
+
return {
|
|
4262
|
+
mode: "surface",
|
|
4263
|
+
target: singleFieldValue2(surfaceField)
|
|
4264
|
+
};
|
|
4265
|
+
}
|
|
4266
|
+
if (freeField) {
|
|
4267
|
+
const raw = singleFieldValue2(freeField);
|
|
4268
|
+
const distance = tryParseAtlasUnitValue(raw);
|
|
4269
|
+
return {
|
|
4270
|
+
mode: "free",
|
|
4271
|
+
distance: distance ?? void 0,
|
|
4272
|
+
descriptor: distance ? void 0 : raw
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
return null;
|
|
4276
|
+
}
|
|
4277
|
+
function normalizeDraftProperties(objectType, fieldMap) {
|
|
4278
|
+
const properties = {};
|
|
4279
|
+
for (const [key, fields] of fieldMap.entries()) {
|
|
4280
|
+
const field = fields[0];
|
|
4281
|
+
const spec = getDraftObjectFieldSpec(key);
|
|
4282
|
+
if (!field || !spec?.legacySchema || spec.legacySchema.placement) {
|
|
4283
|
+
continue;
|
|
4284
|
+
}
|
|
4285
|
+
ensureAtlasFieldSupported(key, objectType, field.location);
|
|
4286
|
+
properties[key] = normalizeLegacyScalarValue(key, field.values, field.location);
|
|
4287
|
+
}
|
|
4288
|
+
return properties;
|
|
4289
|
+
}
|
|
4290
|
+
function normalizeInfoEntries(entries, label) {
|
|
4291
|
+
const normalized = {};
|
|
4292
|
+
for (const entry of entries) {
|
|
4293
|
+
if (entry.key in normalized) {
|
|
4294
|
+
throw WorldOrbitError.fromLocation(`Duplicate ${label} key "${entry.key}"`, entry.location);
|
|
4295
|
+
}
|
|
4296
|
+
normalized[entry.key] = entry.value;
|
|
4297
|
+
}
|
|
4298
|
+
return normalized;
|
|
4299
|
+
}
|
|
4300
|
+
function normalizeTypedBlocks(typedBlockEntries) {
|
|
4301
|
+
const typedBlocks = {};
|
|
4302
|
+
for (const blockName of Object.keys(typedBlockEntries)) {
|
|
4303
|
+
const entries = typedBlockEntries[blockName];
|
|
4304
|
+
if (entries?.length) {
|
|
4305
|
+
typedBlocks[blockName] = normalizeInfoEntries(entries, blockName);
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
return typedBlocks;
|
|
4309
|
+
}
|
|
4310
|
+
function extractRenderHints(fieldMap) {
|
|
4311
|
+
const renderHints = {};
|
|
4312
|
+
const renderLabelField = fieldMap.get("renderLabel")?.[0];
|
|
4313
|
+
const renderOrbitField = fieldMap.get("renderOrbit")?.[0];
|
|
4314
|
+
const renderPriorityField = fieldMap.get("renderPriority")?.[0];
|
|
4315
|
+
if (renderLabelField) {
|
|
4316
|
+
renderHints.renderLabel = parseAtlasBoolean(singleFieldValue2(renderLabelField), "renderLabel", renderLabelField.location);
|
|
4317
|
+
}
|
|
4318
|
+
if (renderOrbitField) {
|
|
4319
|
+
renderHints.renderOrbit = parseAtlasBoolean(singleFieldValue2(renderOrbitField), "renderOrbit", renderOrbitField.location);
|
|
4320
|
+
}
|
|
4321
|
+
if (renderPriorityField) {
|
|
4322
|
+
renderHints.renderPriority = parseAtlasNumber(singleFieldValue2(renderPriorityField), "renderPriority", renderPriorityField.location);
|
|
4323
|
+
}
|
|
4324
|
+
return Object.keys(renderHints).length > 0 ? renderHints : void 0;
|
|
4325
|
+
}
|
|
4326
|
+
function parseResonanceField(field) {
|
|
4327
|
+
if (field.values.length !== 2) {
|
|
4328
|
+
throw WorldOrbitError.fromLocation('Field "resonance" expects "<targetObjectId> <ratio>"', field.location);
|
|
4329
|
+
}
|
|
4330
|
+
const ratio = field.values[1];
|
|
4331
|
+
if (!/^\d+:\d+$/.test(ratio)) {
|
|
4332
|
+
throw WorldOrbitError.fromLocation(`Invalid resonance ratio "${ratio}"`, field.location);
|
|
4333
|
+
}
|
|
4334
|
+
return {
|
|
4335
|
+
targetObjectId: field.values[0],
|
|
4336
|
+
ratio
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
function parseDeriveField(field) {
|
|
4340
|
+
if (field.values.length !== 2) {
|
|
4341
|
+
throw WorldOrbitError.fromLocation('Field "derive" expects "<field> <strategy>"', field.location);
|
|
4342
|
+
}
|
|
4343
|
+
return {
|
|
4344
|
+
field: field.values[0],
|
|
4345
|
+
strategy: field.values[1]
|
|
4346
|
+
};
|
|
4347
|
+
}
|
|
4348
|
+
function parseToleranceField(field) {
|
|
4349
|
+
if (field.values.length !== 2) {
|
|
4350
|
+
throw WorldOrbitError.fromLocation('Field "tolerance" expects "<field> <value>"', field.location);
|
|
4351
|
+
}
|
|
4352
|
+
const rawValue = field.values[1];
|
|
4353
|
+
const unitValue = tryParseAtlasUnitValue(rawValue);
|
|
4354
|
+
const numericValue2 = Number(rawValue);
|
|
4355
|
+
return {
|
|
4356
|
+
field: field.values[0],
|
|
4357
|
+
value: unitValue ?? (Number.isFinite(numericValue2) ? numericValue2 : rawValue)
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
function parseOptionalTokenList(field) {
|
|
4361
|
+
return field ? [...new Set(field.values)] : [];
|
|
4362
|
+
}
|
|
4363
|
+
function parseOptionalJoinedValue(field) {
|
|
4364
|
+
if (!field) {
|
|
4365
|
+
return null;
|
|
4366
|
+
}
|
|
4367
|
+
return field.values.join(" ").trim() || null;
|
|
4368
|
+
}
|
|
4369
|
+
function parseOptionalUnitField(field, key) {
|
|
4370
|
+
return field ? parseAtlasUnitValue(singleFieldValue2(field), field.location, key) : void 0;
|
|
4371
|
+
}
|
|
4372
|
+
function parseOptionalNumberField(field, key) {
|
|
4373
|
+
return field ? parseAtlasNumber(singleFieldValue2(field), key, field.location) : void 0;
|
|
4374
|
+
}
|
|
4375
|
+
function singleFieldValue2(field) {
|
|
4376
|
+
return singleAtlasValue(field.values, field.key, field.location);
|
|
4377
|
+
}
|
|
4378
|
+
function getDraftObjectFieldSpec(key) {
|
|
4379
|
+
return DRAFT_OBJECT_FIELD_SPECS.get(key);
|
|
4380
|
+
}
|
|
4381
|
+
function validateDraftObjectFieldCompatibility(fields, objectType) {
|
|
4382
|
+
for (const field of fields) {
|
|
4383
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
4384
|
+
if (!spec) {
|
|
4385
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4386
|
+
}
|
|
4387
|
+
if (spec.legacySchema) {
|
|
4388
|
+
ensureAtlasFieldSupported(field.key, objectType, field.location);
|
|
4389
|
+
continue;
|
|
4390
|
+
}
|
|
4391
|
+
if ((field.key === "renderLabel" || field.key === "renderOrbit" || field.key === "tidalLock") && field.values.length !== 1) {
|
|
4392
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4397
|
+
if (sourceSchemaVersion === "2.1") {
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
diagnostics.push({
|
|
4401
|
+
code: "parse.schema21.featureCompatibility",
|
|
4402
|
+
severity: "warning",
|
|
4403
|
+
source: "parse",
|
|
4404
|
+
message: `Feature "${featureName}" requires schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
4405
|
+
line: location.line,
|
|
4406
|
+
column: location.column
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
function preprocessAtlasSource(source) {
|
|
4410
|
+
const chars = [...source];
|
|
4411
|
+
const comments = [];
|
|
4412
|
+
let inString = false;
|
|
4413
|
+
let inBlockComment = false;
|
|
4414
|
+
let blockCommentStart = null;
|
|
4415
|
+
let line = 1;
|
|
4416
|
+
let column = 1;
|
|
4417
|
+
for (let index = 0; index < chars.length; index++) {
|
|
4418
|
+
const ch = chars[index];
|
|
4419
|
+
const next = chars[index + 1];
|
|
4420
|
+
if (inBlockComment) {
|
|
4421
|
+
if (ch === "*" && next === "/") {
|
|
4422
|
+
chars[index] = " ";
|
|
4423
|
+
chars[index + 1] = " ";
|
|
4424
|
+
inBlockComment = false;
|
|
4425
|
+
blockCommentStart = null;
|
|
4426
|
+
index++;
|
|
4427
|
+
column += 2;
|
|
4428
|
+
continue;
|
|
4429
|
+
}
|
|
4430
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
4431
|
+
chars[index] = " ";
|
|
4432
|
+
}
|
|
4433
|
+
if (ch === "\n") {
|
|
4434
|
+
line++;
|
|
4435
|
+
column = 1;
|
|
4436
|
+
} else {
|
|
4437
|
+
column++;
|
|
4438
|
+
}
|
|
4439
|
+
continue;
|
|
4440
|
+
}
|
|
4441
|
+
if (!inString && ch === "/" && next === "*") {
|
|
4442
|
+
comments.push({ kind: "block", line, column });
|
|
4443
|
+
chars[index] = " ";
|
|
4444
|
+
chars[index + 1] = " ";
|
|
4445
|
+
inBlockComment = true;
|
|
4446
|
+
blockCommentStart = { line, column };
|
|
4447
|
+
index++;
|
|
4448
|
+
column += 2;
|
|
4449
|
+
continue;
|
|
4450
|
+
}
|
|
4451
|
+
if (!inString && ch === "#" && !isHexColorLiteral(chars, index)) {
|
|
4452
|
+
comments.push({ kind: "line", line, column });
|
|
4453
|
+
chars[index] = " ";
|
|
4454
|
+
let inner = index + 1;
|
|
4455
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
4456
|
+
chars[inner] = " ";
|
|
4457
|
+
inner++;
|
|
4458
|
+
}
|
|
4459
|
+
column += inner - index;
|
|
4460
|
+
index = inner - 1;
|
|
4461
|
+
continue;
|
|
4462
|
+
}
|
|
4463
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
4464
|
+
inString = !inString;
|
|
4465
|
+
}
|
|
4466
|
+
if (ch === "\n") {
|
|
4467
|
+
line++;
|
|
4468
|
+
column = 1;
|
|
4469
|
+
} else {
|
|
4470
|
+
column++;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
if (inBlockComment) {
|
|
4474
|
+
throw WorldOrbitError.fromLocation("Unclosed block comment", blockCommentStart ?? void 0);
|
|
4475
|
+
}
|
|
4476
|
+
return {
|
|
4477
|
+
source: chars.join(""),
|
|
4478
|
+
comments
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
function isHexColorLiteral(chars, start) {
|
|
4482
|
+
let index = start + 1;
|
|
4483
|
+
let length = 0;
|
|
4484
|
+
while (index < chars.length && /[0-9a-f]/i.test(chars[index] ?? "")) {
|
|
4485
|
+
index++;
|
|
4486
|
+
length++;
|
|
4487
|
+
}
|
|
4488
|
+
if (![3, 4, 6, 8].includes(length)) {
|
|
4489
|
+
return false;
|
|
4490
|
+
}
|
|
4491
|
+
const next = chars[index];
|
|
4492
|
+
return next === void 0 || next === " " || next === " " || next === "\r" || next === "\n";
|
|
3330
4493
|
}
|
|
3331
4494
|
|
|
3332
4495
|
// packages/core/dist/load.js
|
|
3333
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0)?$/i;
|
|
4496
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
4497
|
+
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
3334
4498
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
3335
4499
|
function detectWorldOrbitSchemaVersion(source) {
|
|
3336
|
-
for (const line of source.split(/\r?\n/)) {
|
|
4500
|
+
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
3337
4501
|
const trimmed = line.trim();
|
|
3338
4502
|
if (!trimmed) {
|
|
3339
4503
|
continue;
|
|
@@ -3341,6 +4505,9 @@
|
|
|
3341
4505
|
if (LEGACY_DRAFT_SCHEMA_PATTERN.test(trimmed)) {
|
|
3342
4506
|
return "2.0-draft";
|
|
3343
4507
|
}
|
|
4508
|
+
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
4509
|
+
return "2.1";
|
|
4510
|
+
}
|
|
3344
4511
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
3345
4512
|
return "2.0";
|
|
3346
4513
|
}
|
|
@@ -3348,6 +4515,49 @@
|
|
|
3348
4515
|
}
|
|
3349
4516
|
return "1.0";
|
|
3350
4517
|
}
|
|
4518
|
+
function stripCommentsForSchemaDetection(source) {
|
|
4519
|
+
const chars = [...source];
|
|
4520
|
+
let inString = false;
|
|
4521
|
+
let inBlockComment = false;
|
|
4522
|
+
for (let index = 0; index < chars.length; index++) {
|
|
4523
|
+
const ch = chars[index];
|
|
4524
|
+
const next = chars[index + 1];
|
|
4525
|
+
if (inBlockComment) {
|
|
4526
|
+
if (ch === "*" && next === "/") {
|
|
4527
|
+
chars[index] = " ";
|
|
4528
|
+
chars[index + 1] = " ";
|
|
4529
|
+
inBlockComment = false;
|
|
4530
|
+
index++;
|
|
4531
|
+
continue;
|
|
4532
|
+
}
|
|
4533
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
4534
|
+
chars[index] = " ";
|
|
4535
|
+
}
|
|
4536
|
+
continue;
|
|
4537
|
+
}
|
|
4538
|
+
if (!inString && ch === "/" && next === "*") {
|
|
4539
|
+
chars[index] = " ";
|
|
4540
|
+
chars[index + 1] = " ";
|
|
4541
|
+
inBlockComment = true;
|
|
4542
|
+
index++;
|
|
4543
|
+
continue;
|
|
4544
|
+
}
|
|
4545
|
+
if (!inString && ch === "#") {
|
|
4546
|
+
chars[index] = " ";
|
|
4547
|
+
let inner = index + 1;
|
|
4548
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
4549
|
+
chars[inner] = " ";
|
|
4550
|
+
inner++;
|
|
4551
|
+
}
|
|
4552
|
+
index = inner - 1;
|
|
4553
|
+
continue;
|
|
4554
|
+
}
|
|
4555
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
4556
|
+
inString = !inString;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
return chars.join("");
|
|
4560
|
+
}
|
|
3351
4561
|
function loadWorldOrbitSource(source) {
|
|
3352
4562
|
const result = loadWorldOrbitSourceWithDiagnostics(source);
|
|
3353
4563
|
if (!result.ok || !result.value) {
|
|
@@ -3358,36 +4568,36 @@
|
|
|
3358
4568
|
}
|
|
3359
4569
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
3360
4570
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
3361
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft") {
|
|
4571
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
3362
4572
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
3363
4573
|
}
|
|
3364
4574
|
let ast;
|
|
3365
4575
|
try {
|
|
3366
4576
|
ast = parseWorldOrbit(source);
|
|
3367
|
-
} catch (
|
|
4577
|
+
} catch (error2) {
|
|
3368
4578
|
return {
|
|
3369
4579
|
ok: false,
|
|
3370
4580
|
value: null,
|
|
3371
|
-
diagnostics: [diagnosticFromError(
|
|
4581
|
+
diagnostics: [diagnosticFromError(error2, "parse")]
|
|
3372
4582
|
};
|
|
3373
4583
|
}
|
|
3374
4584
|
let document2;
|
|
3375
4585
|
try {
|
|
3376
4586
|
document2 = normalizeDocument(ast);
|
|
3377
|
-
} catch (
|
|
4587
|
+
} catch (error2) {
|
|
3378
4588
|
return {
|
|
3379
4589
|
ok: false,
|
|
3380
4590
|
value: null,
|
|
3381
|
-
diagnostics: [diagnosticFromError(
|
|
4591
|
+
diagnostics: [diagnosticFromError(error2, "normalize")]
|
|
3382
4592
|
};
|
|
3383
4593
|
}
|
|
3384
4594
|
try {
|
|
3385
4595
|
validateDocument(document2);
|
|
3386
|
-
} catch (
|
|
4596
|
+
} catch (error2) {
|
|
3387
4597
|
return {
|
|
3388
4598
|
ok: false,
|
|
3389
4599
|
value: null,
|
|
3390
|
-
diagnostics: [diagnosticFromError(
|
|
4600
|
+
diagnostics: [diagnosticFromError(error2, "validate")]
|
|
3391
4601
|
};
|
|
3392
4602
|
}
|
|
3393
4603
|
return {
|
|
@@ -3407,30 +4617,29 @@
|
|
|
3407
4617
|
let atlasDocument;
|
|
3408
4618
|
try {
|
|
3409
4619
|
atlasDocument = parseWorldOrbitAtlas(source);
|
|
3410
|
-
} catch (
|
|
4620
|
+
} catch (error2) {
|
|
3411
4621
|
return {
|
|
3412
4622
|
ok: false,
|
|
3413
4623
|
value: null,
|
|
3414
|
-
diagnostics: [diagnosticFromError(
|
|
4624
|
+
diagnostics: [diagnosticFromError(error2, "parse", "load.atlas.failed")]
|
|
3415
4625
|
};
|
|
3416
4626
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
document2 = materializeAtlasDocument(atlasDocument);
|
|
3420
|
-
} catch (error) {
|
|
4627
|
+
const atlasDiagnostics = [...atlasDocument.diagnostics];
|
|
4628
|
+
if (atlasDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
3421
4629
|
return {
|
|
3422
4630
|
ok: false,
|
|
3423
4631
|
value: null,
|
|
3424
|
-
diagnostics:
|
|
4632
|
+
diagnostics: atlasDiagnostics
|
|
3425
4633
|
};
|
|
3426
4634
|
}
|
|
4635
|
+
let document2;
|
|
3427
4636
|
try {
|
|
3428
|
-
|
|
3429
|
-
} catch (
|
|
4637
|
+
document2 = materializeAtlasDocument(atlasDocument);
|
|
4638
|
+
} catch (error2) {
|
|
3430
4639
|
return {
|
|
3431
4640
|
ok: false,
|
|
3432
4641
|
value: null,
|
|
3433
|
-
diagnostics: [diagnosticFromError(
|
|
4642
|
+
diagnostics: [diagnosticFromError(error2, "normalize", "load.atlas.materialize.failed")]
|
|
3434
4643
|
};
|
|
3435
4644
|
}
|
|
3436
4645
|
const loaded = {
|
|
@@ -3439,12 +4648,12 @@
|
|
|
3439
4648
|
document: document2,
|
|
3440
4649
|
atlasDocument,
|
|
3441
4650
|
draftDocument: atlasDocument,
|
|
3442
|
-
diagnostics:
|
|
4651
|
+
diagnostics: atlasDiagnostics
|
|
3443
4652
|
};
|
|
3444
4653
|
return {
|
|
3445
4654
|
ok: true,
|
|
3446
4655
|
value: loaded,
|
|
3447
|
-
diagnostics:
|
|
4656
|
+
diagnostics: atlasDiagnostics
|
|
3448
4657
|
};
|
|
3449
4658
|
}
|
|
3450
4659
|
|
|
@@ -3627,6 +4836,7 @@
|
|
|
3627
4836
|
const imageDefinitions = buildImageDefinitions(visibleObjects);
|
|
3628
4837
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
3629
4838
|
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("") : "";
|
|
4839
|
+
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("") : "";
|
|
3630
4840
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
3631
4841
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
3632
4842
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -3661,6 +4871,7 @@
|
|
|
3661
4871
|
.wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
|
|
3662
4872
|
.wo-orbit-front { opacity: 0.9; }
|
|
3663
4873
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
4874
|
+
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
3664
4875
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
3665
4876
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
3666
4877
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -3694,6 +4905,7 @@
|
|
|
3694
4905
|
<g data-worldorbit-world-content="true">
|
|
3695
4906
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
3696
4907
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
4908
|
+
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
3697
4909
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
3698
4910
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
3699
4911
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -3754,10 +4966,11 @@
|
|
|
3754
4966
|
function renderSceneObject(sceneObject, selectedObjectId, theme) {
|
|
3755
4967
|
const { object, x, y, radius, visualRadius } = sceneObject;
|
|
3756
4968
|
const selectionClass = selectedObjectId === sceneObject.objectId ? " wo-object-selected" : "";
|
|
4969
|
+
const kindClass = object.properties.kind ? ` wo-kind-${String(object.properties.kind).toLowerCase().replace(/[^a-z0-9-]/g, "-")}` : "";
|
|
3757
4970
|
const palette = resolveObjectPalette(sceneObject, theme);
|
|
3758
4971
|
const imageMarkup = renderObjectImage(sceneObject);
|
|
3759
4972
|
const outlineMarkup = imageMarkup ? renderObjectBody(object, x, y, radius, palette, { outlineOnly: true }) : "";
|
|
3760
|
-
return `<g class="wo-object wo-object-${object.type}${selectionClass}" data-object-id="${escapeXml(sceneObject.objectId)}" data-parent-id="${escapeAttribute(sceneObject.parentId ?? "")}" data-group-id="${escapeAttribute(sceneObject.groupId ?? "")}" data-render-id="${escapeXml(sceneObject.renderId)}" tabindex="0" role="button" aria-label="${escapeXml(`${object.type} ${sceneObject.objectId}`)}">
|
|
4973
|
+
return `<g class="wo-object wo-object-${object.type}${kindClass}${selectionClass}" data-object-id="${escapeXml(sceneObject.objectId)}" data-parent-id="${escapeAttribute(sceneObject.parentId ?? "")}" data-group-id="${escapeAttribute(sceneObject.groupId ?? "")}" data-render-id="${escapeXml(sceneObject.renderId)}" tabindex="0" role="button" aria-label="${escapeXml(`${object.type} ${sceneObject.objectId}`)}">
|
|
3761
4974
|
<circle class="wo-selection-ring" cx="${x}" cy="${y}" r="${visualRadius + 8}" />
|
|
3762
4975
|
${renderAtmosphere(sceneObject, palette)}
|
|
3763
4976
|
${renderObjectBody(object, x, y, radius, palette)}
|
|
@@ -3791,8 +5004,33 @@
|
|
|
3791
5004
|
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3792
5005
|
case "structure":
|
|
3793
5006
|
return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3794
|
-
case "phenomenon":
|
|
5007
|
+
case "phenomenon": {
|
|
5008
|
+
const kind = String(object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
5009
|
+
if (options.outlineOnly) {
|
|
5010
|
+
if (kind === "black-hole" || kind === "nebula" || kind === "galaxy" || kind === "dwarf-galaxy") {
|
|
5011
|
+
return `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5012
|
+
}
|
|
5013
|
+
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5014
|
+
}
|
|
5015
|
+
if (kind === "black-hole") {
|
|
5016
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.4}" ry="${radius * 0.55}" fill="none" stroke="${palette.accentRing ?? palette.stroke}" stroke-width="3.5" />
|
|
5017
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="2" />`;
|
|
5018
|
+
}
|
|
5019
|
+
if (kind === "galaxy") {
|
|
5020
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.6}" ry="${radius}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
5021
|
+
<ellipse cx="${x}" cy="${y}" rx="${radius * 1.5}" ry="${radius * 0.42}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.2" />
|
|
5022
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.28}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
5023
|
+
}
|
|
5024
|
+
if (kind === "dwarf-galaxy") {
|
|
5025
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 1.6}" ry="${radius * 0.55}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />
|
|
5026
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.25}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
5027
|
+
}
|
|
5028
|
+
if (kind === "nebula") {
|
|
5029
|
+
return `<circle cx="${x}" cy="${y}" r="${radius * 2.2}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
5030
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />`;
|
|
5031
|
+
}
|
|
3795
5032
|
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5033
|
+
}
|
|
3796
5034
|
}
|
|
3797
5035
|
}
|
|
3798
5036
|
function renderAtmosphere(sceneObject, palette) {
|
|
@@ -3861,7 +5099,8 @@
|
|
|
3861
5099
|
}
|
|
3862
5100
|
}
|
|
3863
5101
|
function resolveObjectPalette(sceneObject, theme) {
|
|
3864
|
-
const
|
|
5102
|
+
const kind = String(sceneObject.object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
5103
|
+
const base = basePaletteForType(sceneObject.object.type, kind, theme);
|
|
3865
5104
|
const customFill = sceneObject.fillColor && isColorLike(sceneObject.fillColor) ? sceneObject.fillColor : base.fill;
|
|
3866
5105
|
const albedo = numericValue(sceneObject.object.properties.albedo);
|
|
3867
5106
|
const temperature = numericValue(sceneObject.object.properties.temperature);
|
|
@@ -3877,7 +5116,7 @@
|
|
|
3877
5116
|
tail: sceneObject.object.type === "comet" ? rgbaString(mixColors(fill, "#ffffff", 0.5) ?? fill, 0.72) : void 0
|
|
3878
5117
|
};
|
|
3879
5118
|
}
|
|
3880
|
-
function basePaletteForType(type, theme) {
|
|
5119
|
+
function basePaletteForType(type, kind, theme) {
|
|
3881
5120
|
switch (type) {
|
|
3882
5121
|
case "star":
|
|
3883
5122
|
return {
|
|
@@ -3899,8 +5138,26 @@
|
|
|
3899
5138
|
case "structure":
|
|
3900
5139
|
return { fill: theme.accentStrong, stroke: "#fff2ea" };
|
|
3901
5140
|
case "phenomenon":
|
|
3902
|
-
return
|
|
5141
|
+
return kindPhenomenonPalette(kind);
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
function kindPhenomenonPalette(kind) {
|
|
5145
|
+
if (kind === "galaxy") {
|
|
5146
|
+
return { fill: "rgba(165,125,255,0.55)", stroke: "rgba(210,185,255,0.75)", halo: "rgba(160,120,255,0.10)", core: "#ede0ff" };
|
|
5147
|
+
}
|
|
5148
|
+
if (kind === "dwarf-galaxy") {
|
|
5149
|
+
return { fill: "rgba(190,165,255,0.45)", stroke: "rgba(220,205,255,0.75)", core: "#ddd0ff" };
|
|
3903
5150
|
}
|
|
5151
|
+
if (kind === "black-hole") {
|
|
5152
|
+
return { fill: "#040408", stroke: "#ff6a00", accentRing: "rgba(255,140,20,0.72)" };
|
|
5153
|
+
}
|
|
5154
|
+
if (kind === "nebula") {
|
|
5155
|
+
return { fill: "rgba(105,205,255,0.45)", stroke: "rgba(180,235,255,0.72)", halo: "rgba(100,200,255,0.08)" };
|
|
5156
|
+
}
|
|
5157
|
+
if (kind === "void") {
|
|
5158
|
+
return { fill: "#05080f", stroke: "rgba(130,160,255,0.4)" };
|
|
5159
|
+
}
|
|
5160
|
+
return { fill: "#78ffd7", stroke: "#e9fff7" };
|
|
3904
5161
|
}
|
|
3905
5162
|
function applyTemperatureAndAlbedo(baseColor, temperature, albedo, type) {
|
|
3906
5163
|
let nextColor = baseColor;
|
|
@@ -4220,6 +5477,41 @@
|
|
|
4220
5477
|
});
|
|
4221
5478
|
}
|
|
4222
5479
|
const placement = details.object.placement;
|
|
5480
|
+
if (details.object.groups?.length) {
|
|
5481
|
+
fields.set("groups", {
|
|
5482
|
+
key: "groups",
|
|
5483
|
+
label: "Groups",
|
|
5484
|
+
value: details.object.groups.join(", ")
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
if (details.object.epoch) {
|
|
5488
|
+
fields.set("epoch", {
|
|
5489
|
+
key: "epoch",
|
|
5490
|
+
label: "Epoch",
|
|
5491
|
+
value: details.object.epoch
|
|
5492
|
+
});
|
|
5493
|
+
}
|
|
5494
|
+
if (details.object.referencePlane) {
|
|
5495
|
+
fields.set("referencePlane", {
|
|
5496
|
+
key: "referencePlane",
|
|
5497
|
+
label: "Reference Plane",
|
|
5498
|
+
value: details.object.referencePlane
|
|
5499
|
+
});
|
|
5500
|
+
}
|
|
5501
|
+
if (details.object.tidalLock !== void 0) {
|
|
5502
|
+
fields.set("tidalLock", {
|
|
5503
|
+
key: "tidalLock",
|
|
5504
|
+
label: "Tidal Lock",
|
|
5505
|
+
value: details.object.tidalLock ? "true" : "false"
|
|
5506
|
+
});
|
|
5507
|
+
}
|
|
5508
|
+
if (details.object.resonance) {
|
|
5509
|
+
fields.set("resonance", {
|
|
5510
|
+
key: "resonance",
|
|
5511
|
+
label: "Resonance",
|
|
5512
|
+
value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
|
|
5513
|
+
});
|
|
5514
|
+
}
|
|
4223
5515
|
if (placement?.mode === "at") {
|
|
4224
5516
|
fields.set("placement", {
|
|
4225
5517
|
key: "placement",
|
|
@@ -4389,6 +5681,9 @@
|
|
|
4389
5681
|
touchPoints.set(event.pointerId, point);
|
|
4390
5682
|
if (touchPoints.size === 2) {
|
|
4391
5683
|
touchGesture = createTouchGestureState(scene, state, touchPoints);
|
|
5684
|
+
} else if (touchPoints.size === 1) {
|
|
5685
|
+
dragDistance = 0;
|
|
5686
|
+
suppressClick = false;
|
|
4392
5687
|
}
|
|
4393
5688
|
return;
|
|
4394
5689
|
}
|
|
@@ -4406,7 +5701,9 @@
|
|
|
4406
5701
|
if (!behavior.touch || !touchPoints.has(event.pointerId)) {
|
|
4407
5702
|
return;
|
|
4408
5703
|
}
|
|
4409
|
-
touchPoints.
|
|
5704
|
+
const prevPoint = touchPoints.get(event.pointerId);
|
|
5705
|
+
const nextPoint2 = getViewportPointFromClient(event.clientX, event.clientY);
|
|
5706
|
+
touchPoints.set(event.pointerId, nextPoint2);
|
|
4410
5707
|
if (touchPoints.size === 2) {
|
|
4411
5708
|
if (!touchGesture) {
|
|
4412
5709
|
touchGesture = createTouchGestureState(scene, state, touchPoints);
|
|
@@ -4417,6 +5714,14 @@
|
|
|
4417
5714
|
const deltaX2 = current.center.x - touchGesture.startViewportCenter.x;
|
|
4418
5715
|
const deltaY2 = current.center.y - touchGesture.startViewportCenter.y;
|
|
4419
5716
|
updateState(panViewerState(zoomedState, deltaX2, deltaY2));
|
|
5717
|
+
} else if (touchPoints.size === 1) {
|
|
5718
|
+
const deltaX2 = nextPoint2.x - prevPoint.x;
|
|
5719
|
+
const deltaY2 = nextPoint2.y - prevPoint.y;
|
|
5720
|
+
dragDistance += Math.abs(deltaX2) + Math.abs(deltaY2);
|
|
5721
|
+
if (dragDistance > 2) {
|
|
5722
|
+
suppressClick = true;
|
|
5723
|
+
}
|
|
5724
|
+
updateState(panViewerState(state, deltaX2, deltaY2));
|
|
4420
5725
|
}
|
|
4421
5726
|
return;
|
|
4422
5727
|
}
|
|
@@ -4881,8 +6186,10 @@
|
|
|
4881
6186
|
renderObject,
|
|
4882
6187
|
label: scene.labels.find((label) => label.objectId === renderObject.objectId && !label.hidden) ?? null,
|
|
4883
6188
|
group: scene.groups.find((group) => group.renderId === renderObject.groupId) ?? null,
|
|
6189
|
+
semanticGroups: scene.semanticGroups.filter((group) => renderObject.semanticGroupIds.includes(group.id)),
|
|
4884
6190
|
orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
|
|
4885
6191
|
relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
|
|
6192
|
+
relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
|
|
4886
6193
|
parent: getObjectById(renderObject.parentId),
|
|
4887
6194
|
children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
|
|
4888
6195
|
ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
|
|
@@ -5507,6 +6814,7 @@
|
|
|
5507
6814
|
const controls = {
|
|
5508
6815
|
search: options.controls?.search ?? true,
|
|
5509
6816
|
typeFilter: options.controls?.typeFilter ?? true,
|
|
6817
|
+
groupFilter: options.controls?.groupFilter ?? true,
|
|
5510
6818
|
viewpointSelect: options.controls?.viewpointSelect ?? true,
|
|
5511
6819
|
inspector: options.controls?.inspector ?? true,
|
|
5512
6820
|
bookmarks: options.controls?.bookmarks ?? true
|
|
@@ -5516,6 +6824,7 @@
|
|
|
5516
6824
|
const toolbar = container.querySelector("[data-atlas-toolbar]");
|
|
5517
6825
|
const searchInput = container.querySelector("[data-atlas-search]");
|
|
5518
6826
|
const typeFilterSelect = container.querySelector("[data-atlas-type-filter]");
|
|
6827
|
+
const groupFilterSelect = container.querySelector("[data-atlas-group-filter]");
|
|
5519
6828
|
const viewpointSelect = container.querySelector("[data-atlas-viewpoint]");
|
|
5520
6829
|
const bookmarkButton = container.querySelector("[data-atlas-bookmark]");
|
|
5521
6830
|
const bookmarkList = container.querySelector("[data-atlas-bookmarks]");
|
|
@@ -5528,6 +6837,7 @@
|
|
|
5528
6837
|
const baseFilter = normalizeViewerFilter(options.initialFilter ?? null);
|
|
5529
6838
|
let searchQuery = options.initialQuery?.trim() ?? baseFilter?.query ?? "";
|
|
5530
6839
|
let objectTypeFilter = options.initialObjectType ?? (baseFilter?.objectTypes?.length === 1 ? baseFilter.objectTypes[0] : null);
|
|
6840
|
+
let groupFilter = baseFilter?.groupIds?.[0] ?? null;
|
|
5531
6841
|
let bookmarks = [];
|
|
5532
6842
|
let viewer;
|
|
5533
6843
|
viewer = createInteractiveViewer(stage, {
|
|
@@ -5575,6 +6885,7 @@
|
|
|
5575
6885
|
});
|
|
5576
6886
|
applyCurrentFilter();
|
|
5577
6887
|
populateViewpoints();
|
|
6888
|
+
populateGroups();
|
|
5578
6889
|
syncControlsFromFilter(viewer.getFilter());
|
|
5579
6890
|
renderBookmarks();
|
|
5580
6891
|
updateSearchResults();
|
|
@@ -5587,6 +6898,10 @@
|
|
|
5587
6898
|
objectTypeFilter = typeFilterSelect.value || null;
|
|
5588
6899
|
applyCurrentFilter();
|
|
5589
6900
|
});
|
|
6901
|
+
groupFilterSelect?.addEventListener("change", () => {
|
|
6902
|
+
groupFilter = groupFilterSelect.value || null;
|
|
6903
|
+
applyCurrentFilter();
|
|
6904
|
+
});
|
|
5590
6905
|
viewpointSelect?.addEventListener("change", () => {
|
|
5591
6906
|
const activeViewer = requireViewer();
|
|
5592
6907
|
if (!viewpointSelect.value) {
|
|
@@ -5728,6 +7043,7 @@
|
|
|
5728
7043
|
return api;
|
|
5729
7044
|
function refreshAfterInputChange() {
|
|
5730
7045
|
populateViewpoints();
|
|
7046
|
+
populateGroups();
|
|
5731
7047
|
applyCurrentFilter();
|
|
5732
7048
|
renderBookmarks();
|
|
5733
7049
|
updateSearchResults();
|
|
@@ -5744,19 +7060,23 @@
|
|
|
5744
7060
|
query: searchQuery || void 0,
|
|
5745
7061
|
objectTypes: objectTypeFilter ? [objectTypeFilter] : void 0,
|
|
5746
7062
|
tags: baseFilter?.tags,
|
|
5747
|
-
groupIds: baseFilter?.groupIds,
|
|
7063
|
+
groupIds: groupFilter ? [groupFilter] : baseFilter?.groupIds,
|
|
5748
7064
|
includeAncestors: baseFilter?.includeAncestors ?? true
|
|
5749
7065
|
});
|
|
5750
7066
|
}
|
|
5751
7067
|
function syncControlsFromFilter(filter) {
|
|
5752
7068
|
searchQuery = filter?.query?.trim() ?? "";
|
|
5753
7069
|
objectTypeFilter = filter?.objectTypes?.length === 1 ? filter.objectTypes[0] : null;
|
|
7070
|
+
groupFilter = filter?.groupIds?.length === 1 ? filter.groupIds[0] : null;
|
|
5754
7071
|
if (searchInput && document.activeElement !== searchInput) {
|
|
5755
7072
|
searchInput.value = searchQuery;
|
|
5756
7073
|
}
|
|
5757
7074
|
if (typeFilterSelect) {
|
|
5758
7075
|
typeFilterSelect.value = objectTypeFilter ?? "";
|
|
5759
7076
|
}
|
|
7077
|
+
if (groupFilterSelect) {
|
|
7078
|
+
groupFilterSelect.value = groupFilter ?? "";
|
|
7079
|
+
}
|
|
5760
7080
|
}
|
|
5761
7081
|
function populateViewpoints() {
|
|
5762
7082
|
if (!viewpointSelect) {
|
|
@@ -5770,6 +7090,17 @@
|
|
|
5770
7090
|
].join("");
|
|
5771
7091
|
viewpointSelect.value = active;
|
|
5772
7092
|
}
|
|
7093
|
+
function populateGroups() {
|
|
7094
|
+
if (!groupFilterSelect) {
|
|
7095
|
+
return;
|
|
7096
|
+
}
|
|
7097
|
+
const activeViewer = requireViewer();
|
|
7098
|
+
groupFilterSelect.innerHTML = [
|
|
7099
|
+
`<option value="">All groups</option>`,
|
|
7100
|
+
...activeViewer.getScene().semanticGroups.map((group) => `<option value="${escapeHtml2(group.id)}">${escapeHtml2(group.label)}</option>`)
|
|
7101
|
+
].join("");
|
|
7102
|
+
groupFilterSelect.value = groupFilter ?? "";
|
|
7103
|
+
}
|
|
5773
7104
|
function syncViewpointControl() {
|
|
5774
7105
|
if (!viewpointSelect) {
|
|
5775
7106
|
return;
|
|
@@ -5803,6 +7134,8 @@
|
|
|
5803
7134
|
projection: activeViewer.getScene().projection,
|
|
5804
7135
|
renderPreset: activeViewer.getScene().renderPreset,
|
|
5805
7136
|
groupCount: activeViewer.getScene().groups.length,
|
|
7137
|
+
semanticGroupCount: activeViewer.getScene().semanticGroups.length,
|
|
7138
|
+
relationCount: activeViewer.getScene().relations.length,
|
|
5806
7139
|
viewpointCount: activeViewer.getScene().viewpoints.length
|
|
5807
7140
|
}
|
|
5808
7141
|
};
|
|
@@ -5835,6 +7168,12 @@
|
|
|
5835
7168
|
<option value="phenomenon">Phenomenon</option>
|
|
5836
7169
|
</select>
|
|
5837
7170
|
</label>` : "",
|
|
7171
|
+
controls.groupFilter ? `<label class="wo-atlas-field">
|
|
7172
|
+
<span>Group</span>
|
|
7173
|
+
<select data-atlas-group-filter>
|
|
7174
|
+
<option value="">All groups</option>
|
|
7175
|
+
</select>
|
|
7176
|
+
</label>` : "",
|
|
5838
7177
|
controls.viewpointSelect ? `<label class="wo-atlas-field">
|
|
5839
7178
|
<span>Viewpoint</span>
|
|
5840
7179
|
<select data-atlas-viewpoint>
|