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
|
@@ -302,13 +302,13 @@
|
|
|
302
302
|
function unitFamilyAllowsUnit(family, unit) {
|
|
303
303
|
switch (family) {
|
|
304
304
|
case "distance":
|
|
305
|
-
return unit === null || ["au", "km", "re", "sol"].includes(unit);
|
|
305
|
+
return unit === null || ["au", "km", "m", "ly", "pc", "kpc", "re", "sol"].includes(unit);
|
|
306
306
|
case "radius":
|
|
307
|
-
return unit === null || ["km", "re", "sol"].includes(unit);
|
|
307
|
+
return unit === null || ["km", "m", "re", "rj", "sol"].includes(unit);
|
|
308
308
|
case "mass":
|
|
309
|
-
return unit === null || ["me", "sol"].includes(unit);
|
|
309
|
+
return unit === null || ["me", "mj", "sol"].includes(unit);
|
|
310
310
|
case "duration":
|
|
311
|
-
return unit === null || ["h", "d", "y"].includes(unit);
|
|
311
|
+
return unit === null || ["s", "min", "h", "d", "y", "ky", "my", "gy"].includes(unit);
|
|
312
312
|
case "angle":
|
|
313
313
|
return unit === null || unit === "deg";
|
|
314
314
|
case "generic":
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
// packages/core/dist/normalize.js
|
|
515
|
-
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(au|km|re|sol|
|
|
515
|
+
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)?$/;
|
|
516
516
|
var BOOLEAN_VALUES = /* @__PURE__ */ new Map([
|
|
517
517
|
["true", true],
|
|
518
518
|
["false", false],
|
|
@@ -537,7 +537,10 @@
|
|
|
537
537
|
return {
|
|
538
538
|
format: "worldorbit",
|
|
539
539
|
version: "1.0",
|
|
540
|
+
schemaVersion: "1.0",
|
|
540
541
|
system,
|
|
542
|
+
groups: [],
|
|
543
|
+
relations: [],
|
|
541
544
|
objects
|
|
542
545
|
};
|
|
543
546
|
}
|
|
@@ -547,13 +550,17 @@
|
|
|
547
550
|
const fieldMap = collectFields(mergedFields);
|
|
548
551
|
const placement = extractPlacement(node.objectType, fieldMap);
|
|
549
552
|
const properties = normalizeProperties(fieldMap);
|
|
550
|
-
const
|
|
553
|
+
const info2 = normalizeInfo(node.infoEntries);
|
|
551
554
|
if (node.objectType === "system") {
|
|
552
555
|
return {
|
|
553
556
|
type: "system",
|
|
554
557
|
id: node.name,
|
|
558
|
+
title: typeof properties.title === "string" ? properties.title : null,
|
|
559
|
+
description: null,
|
|
560
|
+
epoch: null,
|
|
561
|
+
referencePlane: null,
|
|
555
562
|
properties,
|
|
556
|
-
info
|
|
563
|
+
info: info2
|
|
557
564
|
};
|
|
558
565
|
}
|
|
559
566
|
return {
|
|
@@ -561,7 +568,7 @@
|
|
|
561
568
|
id: node.name,
|
|
562
569
|
properties,
|
|
563
570
|
placement,
|
|
564
|
-
info
|
|
571
|
+
info: info2
|
|
565
572
|
};
|
|
566
573
|
}
|
|
567
574
|
function validateFieldCompatibility(objectType, fields) {
|
|
@@ -691,14 +698,14 @@
|
|
|
691
698
|
}
|
|
692
699
|
}
|
|
693
700
|
function normalizeInfo(entries) {
|
|
694
|
-
const
|
|
701
|
+
const info2 = {};
|
|
695
702
|
for (const entry of entries) {
|
|
696
|
-
if (entry.key in
|
|
703
|
+
if (entry.key in info2) {
|
|
697
704
|
throw WorldOrbitError.fromLocation(`Duplicate info key "${entry.key}"`, entry.location);
|
|
698
705
|
}
|
|
699
|
-
|
|
706
|
+
info2[entry.key] = entry.value;
|
|
700
707
|
}
|
|
701
|
-
return
|
|
708
|
+
return info2;
|
|
702
709
|
}
|
|
703
710
|
function parseAtReference(target, location) {
|
|
704
711
|
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
@@ -869,37 +876,41 @@
|
|
|
869
876
|
}
|
|
870
877
|
|
|
871
878
|
// packages/core/dist/diagnostics.js
|
|
872
|
-
function diagnosticFromError(
|
|
873
|
-
if (
|
|
879
|
+
function diagnosticFromError(error2, source, code = `${source}.failed`) {
|
|
880
|
+
if (error2 instanceof WorldOrbitError) {
|
|
874
881
|
return {
|
|
875
882
|
code,
|
|
876
883
|
severity: "error",
|
|
877
884
|
source,
|
|
878
|
-
message:
|
|
879
|
-
line:
|
|
880
|
-
column:
|
|
885
|
+
message: error2.message,
|
|
886
|
+
line: error2.line,
|
|
887
|
+
column: error2.column
|
|
881
888
|
};
|
|
882
889
|
}
|
|
883
|
-
if (
|
|
890
|
+
if (error2 instanceof Error) {
|
|
884
891
|
return {
|
|
885
892
|
code,
|
|
886
893
|
severity: "error",
|
|
887
894
|
source,
|
|
888
|
-
message:
|
|
895
|
+
message: error2.message
|
|
889
896
|
};
|
|
890
897
|
}
|
|
891
898
|
return {
|
|
892
899
|
code,
|
|
893
900
|
severity: "error",
|
|
894
901
|
source,
|
|
895
|
-
message: String(
|
|
902
|
+
message: String(error2)
|
|
896
903
|
};
|
|
897
904
|
}
|
|
898
905
|
|
|
899
906
|
// packages/core/dist/scene.js
|
|
900
907
|
var AU_IN_KM = 1495978707e-1;
|
|
901
908
|
var EARTH_RADIUS_IN_KM = 6371;
|
|
909
|
+
var JUPITER_RADIUS_IN_KM = 71492;
|
|
902
910
|
var SOLAR_RADIUS_IN_KM = 695700;
|
|
911
|
+
var LY_IN_AU = 63241.077;
|
|
912
|
+
var PC_IN_AU = 206264.806;
|
|
913
|
+
var KPC_IN_AU = 206264806;
|
|
903
914
|
var ISO_FLATTENING = 0.68;
|
|
904
915
|
var MIN_ISO_MINOR_SCALE = 0.2;
|
|
905
916
|
var ARC_SAMPLE_COUNT = 28;
|
|
@@ -1019,8 +1030,10 @@
|
|
|
1019
1030
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1020
1031
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1021
1032
|
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1022
|
-
const
|
|
1033
|
+
const relations = createSceneRelations(document2, objects);
|
|
1034
|
+
const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
|
|
1023
1035
|
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
|
|
1036
|
+
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1024
1037
|
const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
|
|
1025
1038
|
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1026
1039
|
return {
|
|
@@ -1030,7 +1043,7 @@
|
|
|
1030
1043
|
renderPreset: frame.preset,
|
|
1031
1044
|
projection,
|
|
1032
1045
|
scaleModel,
|
|
1033
|
-
title: String(document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1046
|
+
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1034
1047
|
subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
|
|
1035
1048
|
systemId,
|
|
1036
1049
|
viewMode: projection,
|
|
@@ -1046,9 +1059,11 @@
|
|
|
1046
1059
|
contentBounds,
|
|
1047
1060
|
layers,
|
|
1048
1061
|
groups,
|
|
1062
|
+
semanticGroups,
|
|
1049
1063
|
viewpoints,
|
|
1050
1064
|
objects,
|
|
1051
1065
|
orbitVisuals,
|
|
1066
|
+
relations,
|
|
1052
1067
|
leaders,
|
|
1053
1068
|
labels
|
|
1054
1069
|
};
|
|
@@ -1147,6 +1162,7 @@
|
|
|
1147
1162
|
}
|
|
1148
1163
|
function createSceneObject(position, scaleModel, relationships) {
|
|
1149
1164
|
const { object, x, y, radius, sortKey, anchorX, anchorY } = position;
|
|
1165
|
+
const renderPriority = object.renderHints?.renderPriority ?? 0;
|
|
1150
1166
|
return {
|
|
1151
1167
|
renderId: createRenderId(object.id),
|
|
1152
1168
|
objectId: object.id,
|
|
@@ -1155,11 +1171,12 @@
|
|
|
1155
1171
|
ancestorIds: relationships.ancestorIds.get(object.id) ?? [],
|
|
1156
1172
|
childIds: relationships.childIds.get(object.id) ?? [],
|
|
1157
1173
|
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1174
|
+
semanticGroupIds: [...object.groups ?? []],
|
|
1158
1175
|
x,
|
|
1159
1176
|
y,
|
|
1160
1177
|
radius,
|
|
1161
1178
|
visualRadius: visualExtentForObject(object, radius, scaleModel),
|
|
1162
|
-
sortKey,
|
|
1179
|
+
sortKey: sortKey + renderPriority * 1e-3,
|
|
1163
1180
|
anchorX,
|
|
1164
1181
|
anchorY,
|
|
1165
1182
|
label: object.id,
|
|
@@ -1176,6 +1193,7 @@
|
|
|
1176
1193
|
object: draft.object,
|
|
1177
1194
|
parentId: draft.parentId,
|
|
1178
1195
|
groupId,
|
|
1196
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1179
1197
|
kind: draft.kind,
|
|
1180
1198
|
cx: draft.cx,
|
|
1181
1199
|
cy: draft.cy,
|
|
@@ -1187,7 +1205,7 @@
|
|
|
1187
1205
|
bandThickness: draft.bandThickness,
|
|
1188
1206
|
frontArcPath: draft.frontArcPath,
|
|
1189
1207
|
backArcPath: draft.backArcPath,
|
|
1190
|
-
hidden: draft.object.properties.hidden === true
|
|
1208
|
+
hidden: draft.object.properties.hidden === true || draft.object.renderHints?.renderOrbit === false
|
|
1191
1209
|
};
|
|
1192
1210
|
}
|
|
1193
1211
|
function createLeaderLine(draft) {
|
|
@@ -1196,6 +1214,7 @@
|
|
|
1196
1214
|
objectId: draft.object.id,
|
|
1197
1215
|
object: draft.object,
|
|
1198
1216
|
groupId: draft.groupId,
|
|
1217
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1199
1218
|
x1: draft.x1,
|
|
1200
1219
|
y1: draft.y1,
|
|
1201
1220
|
x2: draft.x2,
|
|
@@ -1207,7 +1226,7 @@
|
|
|
1207
1226
|
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1208
1227
|
const labels = [];
|
|
1209
1228
|
const occupied = [];
|
|
1210
|
-
const visibleObjects = [...objects].filter((object) => !object.hidden).sort((left, right) => left.sortKey - right.sortKey);
|
|
1229
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort((left, right) => left.sortKey - right.sortKey);
|
|
1211
1230
|
for (const object of visibleObjects) {
|
|
1212
1231
|
const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
|
|
1213
1232
|
const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
|
|
@@ -1227,6 +1246,7 @@
|
|
|
1227
1246
|
objectId: object.objectId,
|
|
1228
1247
|
object: object.object,
|
|
1229
1248
|
groupId: object.groupId,
|
|
1249
|
+
semanticGroupIds: [...object.semanticGroupIds],
|
|
1230
1250
|
label: object.label,
|
|
1231
1251
|
secondaryLabel: object.secondaryLabel,
|
|
1232
1252
|
x: object.x,
|
|
@@ -1239,7 +1259,7 @@
|
|
|
1239
1259
|
}
|
|
1240
1260
|
return labels;
|
|
1241
1261
|
}
|
|
1242
|
-
function createSceneLayers(orbitVisuals, leaders, objects, labels) {
|
|
1262
|
+
function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
|
|
1243
1263
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1244
1264
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1245
1265
|
return [
|
|
@@ -1250,6 +1270,10 @@
|
|
|
1250
1270
|
},
|
|
1251
1271
|
{ id: "orbits-back", renderIds: backOrbitIds },
|
|
1252
1272
|
{ id: "orbits-front", renderIds: frontOrbitIds },
|
|
1273
|
+
{
|
|
1274
|
+
id: "relations",
|
|
1275
|
+
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1276
|
+
},
|
|
1253
1277
|
{
|
|
1254
1278
|
id: "objects",
|
|
1255
1279
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1314,6 +1338,36 @@
|
|
|
1314
1338
|
}
|
|
1315
1339
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1316
1340
|
}
|
|
1341
|
+
function createSceneSemanticGroups(document2, objects) {
|
|
1342
|
+
return [...document2.groups].map((group) => ({
|
|
1343
|
+
id: group.id,
|
|
1344
|
+
label: group.label,
|
|
1345
|
+
summary: group.summary,
|
|
1346
|
+
color: group.color,
|
|
1347
|
+
tags: [...group.tags],
|
|
1348
|
+
hidden: group.hidden,
|
|
1349
|
+
objectIds: objects.filter((object) => !object.hidden && object.semanticGroupIds.includes(group.id)).map((object) => object.objectId)
|
|
1350
|
+
})).sort((left, right) => left.label.localeCompare(right.label));
|
|
1351
|
+
}
|
|
1352
|
+
function createSceneRelations(document2, objects) {
|
|
1353
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1354
|
+
return document2.relations.map((relation) => {
|
|
1355
|
+
const from = objectMap.get(relation.from);
|
|
1356
|
+
const to = objectMap.get(relation.to);
|
|
1357
|
+
return {
|
|
1358
|
+
renderId: `${createRenderId(relation.id)}-relation`,
|
|
1359
|
+
relationId: relation.id,
|
|
1360
|
+
relation,
|
|
1361
|
+
fromObjectId: relation.from,
|
|
1362
|
+
toObjectId: relation.to,
|
|
1363
|
+
x1: from?.x ?? 0,
|
|
1364
|
+
y1: from?.y ?? 0,
|
|
1365
|
+
x2: to?.x ?? 0,
|
|
1366
|
+
y2: to?.y ?? 0,
|
|
1367
|
+
hidden: relation.hidden || !from || !to || from.hidden || to.hidden
|
|
1368
|
+
};
|
|
1369
|
+
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1370
|
+
}
|
|
1317
1371
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1318
1372
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1319
1373
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1331,7 +1385,7 @@
|
|
|
1331
1385
|
}
|
|
1332
1386
|
const field = fieldParts.join(".").toLowerCase();
|
|
1333
1387
|
const draft = drafts.get(id) ?? { id };
|
|
1334
|
-
applyViewpointField(draft, field, value, projection, preset, relationships, objectMap);
|
|
1388
|
+
applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap);
|
|
1335
1389
|
drafts.set(id, draft);
|
|
1336
1390
|
}
|
|
1337
1391
|
const viewpoints = [...drafts.values()].map((draft) => finalizeViewpointDraft(draft, projection, preset, objectMap)).filter(Boolean);
|
|
@@ -1359,7 +1413,8 @@
|
|
|
1359
1413
|
});
|
|
1360
1414
|
}
|
|
1361
1415
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1362
|
-
const
|
|
1416
|
+
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1417
|
+
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1363
1418
|
return {
|
|
1364
1419
|
id: "overview",
|
|
1365
1420
|
label,
|
|
@@ -1375,7 +1430,7 @@
|
|
|
1375
1430
|
generated: true
|
|
1376
1431
|
};
|
|
1377
1432
|
}
|
|
1378
|
-
function applyViewpointField(draft, field, value, projection, preset, relationships, objectMap) {
|
|
1433
|
+
function applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap) {
|
|
1379
1434
|
const normalizedValue = value.trim();
|
|
1380
1435
|
switch (field) {
|
|
1381
1436
|
case "label":
|
|
@@ -1442,7 +1497,7 @@
|
|
|
1442
1497
|
case "groups":
|
|
1443
1498
|
draft.filter = {
|
|
1444
1499
|
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1445
|
-
groupIds: parseViewpointGroups(normalizedValue, relationships, objectMap)
|
|
1500
|
+
groupIds: parseViewpointGroups(normalizedValue, document2, relationships, objectMap)
|
|
1446
1501
|
};
|
|
1447
1502
|
return;
|
|
1448
1503
|
}
|
|
@@ -1515,7 +1570,7 @@
|
|
|
1515
1570
|
next["orbits-front"] = enabled;
|
|
1516
1571
|
continue;
|
|
1517
1572
|
}
|
|
1518
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1573
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1519
1574
|
next[rawLayer] = enabled;
|
|
1520
1575
|
}
|
|
1521
1576
|
}
|
|
@@ -1524,8 +1579,11 @@
|
|
|
1524
1579
|
function parseViewpointObjectTypes(value) {
|
|
1525
1580
|
return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
|
|
1526
1581
|
}
|
|
1527
|
-
function parseViewpointGroups(value, relationships, objectMap) {
|
|
1582
|
+
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1528
1583
|
return splitListValue(value).map((entry) => {
|
|
1584
|
+
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
1585
|
+
return entry;
|
|
1586
|
+
}
|
|
1529
1587
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
1530
1588
|
return entry;
|
|
1531
1589
|
}
|
|
@@ -1656,8 +1714,9 @@
|
|
|
1656
1714
|
}
|
|
1657
1715
|
const orbiting = [...context.orbitChildren.get(object.id) ?? []].sort(compareOrbiting);
|
|
1658
1716
|
const orbitMetricContext = computeOrbitMetricContext(orbiting, parent.radius, context.spacingFactor, context.scaleModel);
|
|
1717
|
+
const orbitRadiiPx = resolveOrbitRadiiPx(orbiting, orbitMetricContext);
|
|
1659
1718
|
orbiting.forEach((child, index) => {
|
|
1660
|
-
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, context);
|
|
1719
|
+
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, orbitRadiiPx[index] ?? orbitMetricContext.innerPx, context);
|
|
1661
1720
|
orbitDrafts.push({
|
|
1662
1721
|
object: child,
|
|
1663
1722
|
parentId: object.id,
|
|
@@ -1731,7 +1790,8 @@
|
|
|
1731
1790
|
metricSpread: 0,
|
|
1732
1791
|
innerPx,
|
|
1733
1792
|
stepPx,
|
|
1734
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
1793
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
1794
|
+
minimumGapPx: stepPx * 0.42
|
|
1735
1795
|
};
|
|
1736
1796
|
}
|
|
1737
1797
|
const minMetric = Math.min(...presentMetrics);
|
|
@@ -1744,10 +1804,11 @@
|
|
|
1744
1804
|
metricSpread,
|
|
1745
1805
|
innerPx,
|
|
1746
1806
|
stepPx,
|
|
1747
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
1807
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
1808
|
+
minimumGapPx: stepPx * 0.42
|
|
1748
1809
|
};
|
|
1749
1810
|
}
|
|
1750
|
-
function resolveOrbitGeometry(object, index, count, parent, metricContext, context) {
|
|
1811
|
+
function resolveOrbitGeometry(object, index, count, parent, metricContext, orbitRadiusPx, context) {
|
|
1751
1812
|
const placement = object.placement;
|
|
1752
1813
|
const band = object.type === "belt" || object.type === "ring";
|
|
1753
1814
|
if (!placement || placement.mode !== "orbit") {
|
|
@@ -1765,7 +1826,7 @@
|
|
|
1765
1826
|
};
|
|
1766
1827
|
}
|
|
1767
1828
|
const eccentricity = clampNumber(typeof placement.eccentricity === "number" ? placement.eccentricity : 0, 0, 0.92);
|
|
1768
|
-
const semiMajor =
|
|
1829
|
+
const semiMajor = orbitRadiusPx;
|
|
1769
1830
|
const baseMinor = Math.max(semiMajor * Math.sqrt(1 - eccentricity * eccentricity), semiMajor * 0.18);
|
|
1770
1831
|
const inclinationDeg = unitValueToDegrees(placement.inclination) ?? 0;
|
|
1771
1832
|
const inclinationScale = context.projection === "isometric" ? Math.max(MIN_ISO_MINOR_SCALE, Math.cos(degreesToRadians(inclinationDeg))) * ISO_FLATTENING : 1;
|
|
@@ -1795,15 +1856,19 @@
|
|
|
1795
1856
|
objectY: objectPoint.y
|
|
1796
1857
|
};
|
|
1797
1858
|
}
|
|
1798
|
-
function resolveOrbitRadiusPx(
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1859
|
+
function resolveOrbitRadiusPx(metric, metricContext) {
|
|
1860
|
+
return metricContext.innerPx + metricContext.stepPx * log2(Math.max(metric, 0) + 1);
|
|
1861
|
+
}
|
|
1862
|
+
function resolveOrbitRadiiPx(objects, metricContext) {
|
|
1863
|
+
const radii = [];
|
|
1864
|
+
objects.forEach((object, index) => {
|
|
1865
|
+
const metric = orbitMetric(object);
|
|
1866
|
+
const fallbackRadius = metricContext.innerPx + index * metricContext.stepPx;
|
|
1867
|
+
const baseRadius = metric === null ? fallbackRadius : resolveOrbitRadiusPx(metric, metricContext);
|
|
1868
|
+
const minimumRadius = index === 0 ? metricContext.innerPx : (radii[index - 1] ?? metricContext.innerPx) + metricContext.minimumGapPx;
|
|
1869
|
+
radii.push(Math.max(baseRadius, minimumRadius));
|
|
1870
|
+
});
|
|
1871
|
+
return radii;
|
|
1807
1872
|
}
|
|
1808
1873
|
function orbitMetric(object) {
|
|
1809
1874
|
if (!object.placement || object.placement.mode !== "orbit") {
|
|
@@ -1811,6 +1876,9 @@
|
|
|
1811
1876
|
}
|
|
1812
1877
|
return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
|
|
1813
1878
|
}
|
|
1879
|
+
function log2(value) {
|
|
1880
|
+
return Math.log(value) / Math.log(2);
|
|
1881
|
+
}
|
|
1814
1882
|
function resolveOrbitPhase(phase, index, count) {
|
|
1815
1883
|
const degreeValue = phase ? unitValueToDegrees(phase) : null;
|
|
1816
1884
|
if (degreeValue !== null) {
|
|
@@ -2134,8 +2202,18 @@
|
|
|
2134
2202
|
return value.value;
|
|
2135
2203
|
case "km":
|
|
2136
2204
|
return value.value / AU_IN_KM;
|
|
2205
|
+
case "m":
|
|
2206
|
+
return value.value / 1e3 / AU_IN_KM;
|
|
2207
|
+
case "ly":
|
|
2208
|
+
return value.value * LY_IN_AU;
|
|
2209
|
+
case "pc":
|
|
2210
|
+
return value.value * PC_IN_AU;
|
|
2211
|
+
case "kpc":
|
|
2212
|
+
return value.value * KPC_IN_AU;
|
|
2137
2213
|
case "re":
|
|
2138
2214
|
return value.value * EARTH_RADIUS_IN_KM / AU_IN_KM;
|
|
2215
|
+
case "rj":
|
|
2216
|
+
return value.value * JUPITER_RADIUS_IN_KM / AU_IN_KM;
|
|
2139
2217
|
case "sol":
|
|
2140
2218
|
return value.value * SOLAR_RADIUS_IN_KM / AU_IN_KM;
|
|
2141
2219
|
default:
|
|
@@ -2275,19 +2353,37 @@
|
|
|
2275
2353
|
const system = document2.system ? {
|
|
2276
2354
|
type: "system",
|
|
2277
2355
|
id: document2.system.id,
|
|
2356
|
+
title: document2.system.title,
|
|
2357
|
+
description: document2.system.description,
|
|
2358
|
+
epoch: document2.system.epoch,
|
|
2359
|
+
referencePlane: document2.system.referencePlane,
|
|
2278
2360
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2279
2361
|
info: materializeDraftSystemInfo(document2.system)
|
|
2280
2362
|
} : null;
|
|
2281
2363
|
return {
|
|
2282
2364
|
format: "worldorbit",
|
|
2283
2365
|
version: "1.0",
|
|
2366
|
+
schemaVersion: document2.version,
|
|
2284
2367
|
system,
|
|
2368
|
+
groups: structuredClone(document2.groups ?? []),
|
|
2369
|
+
relations: structuredClone(document2.relations ?? []),
|
|
2285
2370
|
objects: document2.objects.map(cloneWorldOrbitObject)
|
|
2286
2371
|
};
|
|
2287
2372
|
}
|
|
2288
2373
|
function cloneWorldOrbitObject(object) {
|
|
2289
2374
|
return {
|
|
2290
2375
|
...object,
|
|
2376
|
+
groups: object.groups ? [...object.groups] : void 0,
|
|
2377
|
+
resonance: object.resonance ? { ...object.resonance } : object.resonance,
|
|
2378
|
+
renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
|
|
2379
|
+
deriveRules: object.deriveRules ? object.deriveRules.map((rule) => ({ ...rule })) : void 0,
|
|
2380
|
+
validationRules: object.validationRules ? object.validationRules.map((rule) => ({ ...rule })) : void 0,
|
|
2381
|
+
lockedFields: object.lockedFields ? [...object.lockedFields] : void 0,
|
|
2382
|
+
tolerances: object.tolerances ? object.tolerances.map((entry) => ({
|
|
2383
|
+
field: entry.field,
|
|
2384
|
+
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
|
|
2385
|
+
})) : void 0,
|
|
2386
|
+
typedBlocks: object.typedBlocks ? Object.fromEntries(Object.entries(object.typedBlocks).map(([key, block]) => [key, { ...block ?? {} }])) : void 0,
|
|
2291
2387
|
properties: cloneProperties(object.properties),
|
|
2292
2388
|
placement: object.placement ? structuredClone(object.placement) : null,
|
|
2293
2389
|
info: { ...object.info }
|
|
@@ -2323,71 +2419,80 @@
|
|
|
2323
2419
|
if (system.defaults.units) {
|
|
2324
2420
|
properties.units = system.defaults.units;
|
|
2325
2421
|
}
|
|
2422
|
+
if (system.description) {
|
|
2423
|
+
properties.description = system.description;
|
|
2424
|
+
}
|
|
2425
|
+
if (system.epoch) {
|
|
2426
|
+
properties.epoch = system.epoch;
|
|
2427
|
+
}
|
|
2428
|
+
if (system.referencePlane) {
|
|
2429
|
+
properties.referencePlane = system.referencePlane;
|
|
2430
|
+
}
|
|
2326
2431
|
return properties;
|
|
2327
2432
|
}
|
|
2328
2433
|
function materializeDraftSystemInfo(system) {
|
|
2329
|
-
const
|
|
2434
|
+
const info2 = {
|
|
2330
2435
|
...system.atlasMetadata
|
|
2331
2436
|
};
|
|
2332
2437
|
if (system.defaults.theme) {
|
|
2333
|
-
|
|
2438
|
+
info2["atlas.theme"] = system.defaults.theme;
|
|
2334
2439
|
}
|
|
2335
2440
|
for (const viewpoint of system.viewpoints) {
|
|
2336
2441
|
const prefix = `viewpoint.${viewpoint.id}`;
|
|
2337
|
-
|
|
2442
|
+
info2[`${prefix}.label`] = viewpoint.label;
|
|
2338
2443
|
if (viewpoint.summary) {
|
|
2339
|
-
|
|
2444
|
+
info2[`${prefix}.summary`] = viewpoint.summary;
|
|
2340
2445
|
}
|
|
2341
2446
|
if (viewpoint.focusObjectId) {
|
|
2342
|
-
|
|
2447
|
+
info2[`${prefix}.focus`] = viewpoint.focusObjectId;
|
|
2343
2448
|
}
|
|
2344
2449
|
if (viewpoint.selectedObjectId) {
|
|
2345
|
-
|
|
2450
|
+
info2[`${prefix}.select`] = viewpoint.selectedObjectId;
|
|
2346
2451
|
}
|
|
2347
2452
|
if (viewpoint.projection) {
|
|
2348
|
-
|
|
2453
|
+
info2[`${prefix}.projection`] = viewpoint.projection;
|
|
2349
2454
|
}
|
|
2350
2455
|
if (viewpoint.preset) {
|
|
2351
|
-
|
|
2456
|
+
info2[`${prefix}.preset`] = viewpoint.preset;
|
|
2352
2457
|
}
|
|
2353
2458
|
if (viewpoint.zoom !== null) {
|
|
2354
|
-
|
|
2459
|
+
info2[`${prefix}.zoom`] = String(viewpoint.zoom);
|
|
2355
2460
|
}
|
|
2356
2461
|
if (viewpoint.rotationDeg !== 0) {
|
|
2357
|
-
|
|
2462
|
+
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2358
2463
|
}
|
|
2359
2464
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2360
2465
|
if (serializedLayers) {
|
|
2361
|
-
|
|
2466
|
+
info2[`${prefix}.layers`] = serializedLayers;
|
|
2362
2467
|
}
|
|
2363
2468
|
if (viewpoint.filter?.query) {
|
|
2364
|
-
|
|
2469
|
+
info2[`${prefix}.query`] = viewpoint.filter.query;
|
|
2365
2470
|
}
|
|
2366
2471
|
if ((viewpoint.filter?.objectTypes.length ?? 0) > 0) {
|
|
2367
|
-
|
|
2472
|
+
info2[`${prefix}.types`] = viewpoint.filter?.objectTypes.join(" ") ?? "";
|
|
2368
2473
|
}
|
|
2369
2474
|
if ((viewpoint.filter?.tags.length ?? 0) > 0) {
|
|
2370
|
-
|
|
2475
|
+
info2[`${prefix}.tags`] = viewpoint.filter?.tags.join(" ") ?? "";
|
|
2371
2476
|
}
|
|
2372
2477
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2373
|
-
|
|
2478
|
+
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2374
2479
|
}
|
|
2375
2480
|
}
|
|
2376
2481
|
for (const annotation of system.annotations) {
|
|
2377
2482
|
const prefix = `annotation.${annotation.id}`;
|
|
2378
|
-
|
|
2483
|
+
info2[`${prefix}.label`] = annotation.label;
|
|
2379
2484
|
if (annotation.targetObjectId) {
|
|
2380
|
-
|
|
2485
|
+
info2[`${prefix}.target`] = annotation.targetObjectId;
|
|
2381
2486
|
}
|
|
2382
|
-
|
|
2487
|
+
info2[`${prefix}.body`] = annotation.body;
|
|
2383
2488
|
if (annotation.tags.length > 0) {
|
|
2384
|
-
|
|
2489
|
+
info2[`${prefix}.tags`] = annotation.tags.join(" ");
|
|
2385
2490
|
}
|
|
2386
2491
|
if (annotation.sourceObjectId) {
|
|
2387
|
-
|
|
2492
|
+
info2[`${prefix}.source`] = annotation.sourceObjectId;
|
|
2388
2493
|
}
|
|
2389
2494
|
}
|
|
2390
|
-
return
|
|
2495
|
+
return info2;
|
|
2391
2496
|
}
|
|
2392
2497
|
function serializeViewpointLayers(layers) {
|
|
2393
2498
|
const tokens = [];
|
|
@@ -2396,7 +2501,7 @@
|
|
|
2396
2501
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2397
2502
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2398
2503
|
}
|
|
2399
|
-
for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
|
|
2504
|
+
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
2400
2505
|
if (layers[key] !== void 0) {
|
|
2401
2506
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2402
2507
|
}
|
|
@@ -2404,21 +2509,530 @@
|
|
|
2404
2509
|
return tokens.join(" ");
|
|
2405
2510
|
}
|
|
2406
2511
|
|
|
2512
|
+
// packages/core/dist/atlas-utils.js
|
|
2513
|
+
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)?$/;
|
|
2514
|
+
var BOOLEAN_VALUES2 = /* @__PURE__ */ new Map([
|
|
2515
|
+
["true", true],
|
|
2516
|
+
["false", false],
|
|
2517
|
+
["yes", true],
|
|
2518
|
+
["no", false]
|
|
2519
|
+
]);
|
|
2520
|
+
var URL_SCHEME_PATTERN2 = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
2521
|
+
function normalizeIdentifier(value) {
|
|
2522
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2523
|
+
}
|
|
2524
|
+
function humanizeIdentifier2(value) {
|
|
2525
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
2526
|
+
}
|
|
2527
|
+
function parseAtlasUnitValue(input, location, fieldKey) {
|
|
2528
|
+
const match = input.match(UNIT_PATTERN2);
|
|
2529
|
+
if (!match) {
|
|
2530
|
+
throw WorldOrbitError.fromLocation(`Invalid unit value "${input}"`, location);
|
|
2531
|
+
}
|
|
2532
|
+
const unitValue = {
|
|
2533
|
+
value: Number(match[1]),
|
|
2534
|
+
unit: match[2] ?? null
|
|
2535
|
+
};
|
|
2536
|
+
if (fieldKey) {
|
|
2537
|
+
const schema = getFieldSchema(fieldKey);
|
|
2538
|
+
if (schema?.unitFamily && !unitFamilyAllowsUnit(schema.unitFamily, unitValue.unit)) {
|
|
2539
|
+
throw WorldOrbitError.fromLocation(`Unit "${unitValue.unit ?? "none"}" is not valid for "${fieldKey}"`, location);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return unitValue;
|
|
2543
|
+
}
|
|
2544
|
+
function tryParseAtlasUnitValue(input) {
|
|
2545
|
+
const match = input.match(UNIT_PATTERN2);
|
|
2546
|
+
if (!match) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
value: Number(match[1]),
|
|
2551
|
+
unit: match[2] ?? null
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
function parseAtlasNumber(input, key, location) {
|
|
2555
|
+
const value = Number(input);
|
|
2556
|
+
if (!Number.isFinite(value)) {
|
|
2557
|
+
throw WorldOrbitError.fromLocation(`Invalid numeric value "${input}" for "${key}"`, location);
|
|
2558
|
+
}
|
|
2559
|
+
return value;
|
|
2560
|
+
}
|
|
2561
|
+
function parseAtlasBoolean(input, key, location) {
|
|
2562
|
+
const parsed = BOOLEAN_VALUES2.get(input.toLowerCase());
|
|
2563
|
+
if (parsed === void 0) {
|
|
2564
|
+
throw WorldOrbitError.fromLocation(`Invalid boolean value "${input}" for "${key}"`, location);
|
|
2565
|
+
}
|
|
2566
|
+
return parsed;
|
|
2567
|
+
}
|
|
2568
|
+
function parseAtlasAtReference(target, location) {
|
|
2569
|
+
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
2570
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
2571
|
+
}
|
|
2572
|
+
const pairedMatch = target.match(/^([A-Za-z0-9._-]+)-([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
2573
|
+
if (pairedMatch) {
|
|
2574
|
+
return {
|
|
2575
|
+
kind: "lagrange",
|
|
2576
|
+
primary: pairedMatch[1],
|
|
2577
|
+
secondary: pairedMatch[2],
|
|
2578
|
+
point: pairedMatch[3]
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
const simpleMatch = target.match(/^([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
2582
|
+
if (simpleMatch) {
|
|
2583
|
+
return {
|
|
2584
|
+
kind: "lagrange",
|
|
2585
|
+
primary: simpleMatch[1],
|
|
2586
|
+
secondary: null,
|
|
2587
|
+
point: simpleMatch[2]
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
if (/^[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
2591
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
2592
|
+
}
|
|
2593
|
+
const anchorMatch = target.match(/^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+)$/);
|
|
2594
|
+
if (anchorMatch) {
|
|
2595
|
+
return {
|
|
2596
|
+
kind: "anchor",
|
|
2597
|
+
objectId: anchorMatch[1],
|
|
2598
|
+
anchor: anchorMatch[2]
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
return {
|
|
2602
|
+
kind: "named",
|
|
2603
|
+
name: target
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
function validateAtlasImageSource(value, location) {
|
|
2607
|
+
if (!value) {
|
|
2608
|
+
throw WorldOrbitError.fromLocation('Field "image" must not be empty', location);
|
|
2609
|
+
}
|
|
2610
|
+
if (value.startsWith("//")) {
|
|
2611
|
+
throw WorldOrbitError.fromLocation('Field "image" must use a relative path, root-relative path, or an http/https URL', location);
|
|
2612
|
+
}
|
|
2613
|
+
const schemeMatch = value.match(URL_SCHEME_PATTERN2);
|
|
2614
|
+
if (!schemeMatch) {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const scheme = schemeMatch[0].slice(0, -1).toLowerCase();
|
|
2618
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
2619
|
+
throw WorldOrbitError.fromLocation(`Field "image" does not support the "${scheme}" scheme`, location);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function normalizeLegacyScalarValue(key, values, location) {
|
|
2623
|
+
const schema = getFieldSchema(key);
|
|
2624
|
+
if (!schema) {
|
|
2625
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
2626
|
+
}
|
|
2627
|
+
if (schema.arity === "single" && values.length !== 1) {
|
|
2628
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
2629
|
+
}
|
|
2630
|
+
switch (schema.kind) {
|
|
2631
|
+
case "list":
|
|
2632
|
+
return values;
|
|
2633
|
+
case "boolean":
|
|
2634
|
+
return parseAtlasBoolean(singleAtlasValue(values, key, location), key, location);
|
|
2635
|
+
case "number":
|
|
2636
|
+
return parseAtlasNumber(singleAtlasValue(values, key, location), key, location);
|
|
2637
|
+
case "unit":
|
|
2638
|
+
return parseAtlasUnitValue(singleAtlasValue(values, key, location), location, key);
|
|
2639
|
+
case "string": {
|
|
2640
|
+
const value = values.join(" ").trim();
|
|
2641
|
+
if (key === "image") {
|
|
2642
|
+
validateAtlasImageSource(value, location);
|
|
2643
|
+
}
|
|
2644
|
+
return value;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
function ensureAtlasFieldSupported(key, objectType, location) {
|
|
2649
|
+
const schema = getFieldSchema(key);
|
|
2650
|
+
if (!schema) {
|
|
2651
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
2652
|
+
}
|
|
2653
|
+
if (!schema.objectTypes.includes(objectType)) {
|
|
2654
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" is not valid on "${objectType}"`, location);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
function singleAtlasValue(values, key, location) {
|
|
2658
|
+
if (values.length !== 1) {
|
|
2659
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
2660
|
+
}
|
|
2661
|
+
return values[0];
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
// packages/core/dist/atlas-validate.js
|
|
2665
|
+
var SURFACE_TARGET_TYPES2 = /* @__PURE__ */ new Set(["star", "planet", "moon", "asteroid", "comet"]);
|
|
2666
|
+
var EARTH_MASSES_PER_SOLAR = 332946.0487;
|
|
2667
|
+
var JUPITER_MASSES_PER_SOLAR = 1047.3486;
|
|
2668
|
+
var AU_IN_KM2 = 1495978707e-1;
|
|
2669
|
+
var EARTH_RADIUS_IN_KM2 = 6371;
|
|
2670
|
+
var SOLAR_RADIUS_IN_KM2 = 695700;
|
|
2671
|
+
var LY_IN_AU2 = 63241.077;
|
|
2672
|
+
var PC_IN_AU2 = 206264.806;
|
|
2673
|
+
var KPC_IN_AU2 = 206264806;
|
|
2674
|
+
function collectAtlasDiagnostics(document2, sourceSchemaVersion) {
|
|
2675
|
+
const diagnostics = [];
|
|
2676
|
+
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
2677
|
+
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
2678
|
+
if (!document2.system) {
|
|
2679
|
+
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
2680
|
+
}
|
|
2681
|
+
const knownIds = /* @__PURE__ */ new Map();
|
|
2682
|
+
for (const [kind, ids] of [
|
|
2683
|
+
["group", document2.groups.map((group) => group.id)],
|
|
2684
|
+
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
2685
|
+
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
2686
|
+
["relation", document2.relations.map((relation) => relation.id)],
|
|
2687
|
+
["object", document2.objects.map((object) => object.id)]
|
|
2688
|
+
]) {
|
|
2689
|
+
for (const id of ids) {
|
|
2690
|
+
const previous = knownIds.get(id);
|
|
2691
|
+
if (previous) {
|
|
2692
|
+
diagnostics.push(error("validate.id.duplicate", `Duplicate ${kind} id "${id}" already used by ${previous}.`));
|
|
2693
|
+
} else {
|
|
2694
|
+
knownIds.set(id, kind);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
for (const relation of document2.relations) {
|
|
2699
|
+
validateRelation(relation, objectMap, diagnostics);
|
|
2700
|
+
}
|
|
2701
|
+
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
2702
|
+
validateViewpointFilter(viewpoint.filter, groupIds, sourceSchemaVersion, diagnostics, viewpoint.id);
|
|
2703
|
+
}
|
|
2704
|
+
for (const object of document2.objects) {
|
|
2705
|
+
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
2706
|
+
}
|
|
2707
|
+
return diagnostics;
|
|
2708
|
+
}
|
|
2709
|
+
function validateRelation(relation, objectMap, diagnostics) {
|
|
2710
|
+
if (!relation.from) {
|
|
2711
|
+
diagnostics.push(error("validate.relation.from.required", `Relation "${relation.id}" is missing a "from" target.`));
|
|
2712
|
+
} else if (!objectMap.has(relation.from)) {
|
|
2713
|
+
diagnostics.push(error("validate.relation.from.unknown", `Unknown relation source "${relation.from}" on "${relation.id}".`));
|
|
2714
|
+
}
|
|
2715
|
+
if (!relation.to) {
|
|
2716
|
+
diagnostics.push(error("validate.relation.to.required", `Relation "${relation.id}" is missing a "to" target.`));
|
|
2717
|
+
} else if (!objectMap.has(relation.to)) {
|
|
2718
|
+
diagnostics.push(error("validate.relation.to.unknown", `Unknown relation target "${relation.to}" on "${relation.id}".`));
|
|
2719
|
+
}
|
|
2720
|
+
if (!relation.kind) {
|
|
2721
|
+
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
function validateViewpointFilter(filter, groupIds, sourceSchemaVersion, diagnostics, viewpointId) {
|
|
2725
|
+
if (!filter || sourceSchemaVersion !== "2.1") {
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
for (const groupId of filter.groupIds) {
|
|
2729
|
+
if (!groupIds.has(groupId)) {
|
|
2730
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`));
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
2735
|
+
const placement = object.placement;
|
|
2736
|
+
const orbitPlacement = placement?.mode === "orbit" ? placement : null;
|
|
2737
|
+
const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
|
|
2738
|
+
if (object.groups) {
|
|
2739
|
+
for (const groupId of object.groups) {
|
|
2740
|
+
if (!groupIds.has(groupId)) {
|
|
2741
|
+
diagnostics.push(warn("validate.group.unknown", `Unknown group "${groupId}" on "${object.id}".`, object.id, "groups"));
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
if (orbitPlacement) {
|
|
2746
|
+
if (!objectMap.has(orbitPlacement.target)) {
|
|
2747
|
+
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
2748
|
+
}
|
|
2749
|
+
if (orbitPlacement.distance && orbitPlacement.semiMajor) {
|
|
2750
|
+
diagnostics.push(error("validate.orbit.distanceConflict", `Object "${object.id}" cannot declare both "distance" and "semiMajor".`, object.id, "distance"));
|
|
2751
|
+
}
|
|
2752
|
+
if (orbitPlacement.phase && !object.epoch && !system?.epoch) {
|
|
2753
|
+
diagnostics.push(warn("validate.phase.epochMissing", `Object "${object.id}" sets "phase" without an object or system epoch.`, object.id, "phase"));
|
|
2754
|
+
}
|
|
2755
|
+
if (orbitPlacement.inclination && !object.referencePlane && !system?.referencePlane) {
|
|
2756
|
+
diagnostics.push(warn("validate.inclination.referencePlaneMissing", `Object "${object.id}" sets "inclination" without an object or system reference plane.`, object.id, "inclination"));
|
|
2757
|
+
}
|
|
2758
|
+
if (orbitPlacement.period && !massInSolar(parentObject?.properties.mass)) {
|
|
2759
|
+
diagnostics.push(warn("validate.period.massMissing", `Object "${object.id}" sets "period" but its central mass cannot be derived.`, object.id, "period"));
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
if (placement?.mode === "surface") {
|
|
2763
|
+
const target = objectMap.get(placement.target);
|
|
2764
|
+
if (!target) {
|
|
2765
|
+
diagnostics.push(error("validate.surface.target.unknown", `Unknown placement target "${placement.target}" on "${object.id}".`, object.id, "surface"));
|
|
2766
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
2767
|
+
diagnostics.push(error("validate.surface.target.invalid", `Surface target "${placement.target}" on "${object.id}" is not surface-capable.`, object.id, "surface"));
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (placement?.mode === "at") {
|
|
2771
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
2772
|
+
diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
|
|
2773
|
+
}
|
|
2774
|
+
if (!validateAtTarget(object, objectMap, diagnostics)) {
|
|
2775
|
+
diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (object.resonance) {
|
|
2779
|
+
const target = objectMap.get(object.resonance.targetObjectId);
|
|
2780
|
+
if (!target) {
|
|
2781
|
+
diagnostics.push(error("validate.resonance.target.unknown", `Unknown resonance target "${object.resonance.targetObjectId}" on "${object.id}".`, object.id, "resonance"));
|
|
2782
|
+
} else if (object.placement?.mode !== "orbit" || target.placement?.mode !== "orbit" || object.placement.target !== target.placement.target) {
|
|
2783
|
+
diagnostics.push(warn("validate.resonance.orbitMismatch", `Resonance target "${object.resonance.targetObjectId}" on "${object.id}" does not share a compatible orbital parent.`, object.id, "resonance"));
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
for (const rule of object.deriveRules ?? []) {
|
|
2787
|
+
if (rule.field !== "period" || rule.strategy !== "kepler") {
|
|
2788
|
+
diagnostics.push(warn("validate.derive.unsupported", `Unsupported derive rule "${rule.field} ${rule.strategy}" on "${object.id}".`, object.id, "derive"));
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
2792
|
+
if (derivedPeriodDays === null) {
|
|
2793
|
+
diagnostics.push(warn("validate.derive.inputsMissing", `Object "${object.id}" requests "derive period kepler" but lacks enough input data.`, object.id, "derive"));
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
if (!orbitPlacement?.period) {
|
|
2797
|
+
diagnostics.push(info("validate.derive.period.available", `Object "${object.id}" can derive a Kepler period of ${formatDays(derivedPeriodDays)}.`, object.id, "derive"));
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
for (const rule of object.validationRules ?? []) {
|
|
2801
|
+
if (rule.rule !== "kepler") {
|
|
2802
|
+
diagnostics.push(warn("validate.rule.unsupported", `Unsupported validation rule "${rule.rule}" on "${object.id}".`, object.id, "validate"));
|
|
2803
|
+
continue;
|
|
2804
|
+
}
|
|
2805
|
+
const actualPeriodDays = durationInDays(orbitPlacement?.period);
|
|
2806
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
2807
|
+
if (actualPeriodDays === null || derivedPeriodDays === null) {
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
const toleranceDays = toleranceForField(object, "period");
|
|
2811
|
+
if (Math.abs(actualPeriodDays - derivedPeriodDays) > toleranceDays) {
|
|
2812
|
+
diagnostics.push(error("validate.kepler.mismatch", `Object "${object.id}" fails Kepler validation for "period".`, object.id, "validate"));
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
function validateAtTarget(object, objectMap, diagnostics) {
|
|
2817
|
+
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
2818
|
+
if (!reference) {
|
|
2819
|
+
return true;
|
|
2820
|
+
}
|
|
2821
|
+
if (reference.kind === "named") {
|
|
2822
|
+
return objectMap.has(reference.name);
|
|
2823
|
+
}
|
|
2824
|
+
if (reference.kind === "anchor") {
|
|
2825
|
+
if (!objectMap.has(reference.objectId)) {
|
|
2826
|
+
diagnostics.push(error("validate.anchor.target.unknown", `Unknown anchor target "${reference.objectId}" on "${object.id}".`, object.id, "at"));
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
return true;
|
|
2830
|
+
}
|
|
2831
|
+
if (!objectMap.has(reference.primary)) {
|
|
2832
|
+
diagnostics.push(error("validate.lagrange.primary.unknown", `Unknown Lagrange reference "${reference.primary}" on "${object.id}".`, object.id, "at"));
|
|
2833
|
+
return false;
|
|
2834
|
+
}
|
|
2835
|
+
if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
2836
|
+
diagnostics.push(error("validate.lagrange.secondary.unknown", `Unknown Lagrange reference "${reference.secondary}" on "${object.id}".`, object.id, "at"));
|
|
2837
|
+
return false;
|
|
2838
|
+
}
|
|
2839
|
+
return true;
|
|
2840
|
+
}
|
|
2841
|
+
function keplerPeriodDays(object, parentObject) {
|
|
2842
|
+
const placement = object.placement;
|
|
2843
|
+
if (!placement || placement.mode !== "orbit") {
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
const semiMajorAu = distanceInAu(placement.semiMajor ?? placement.distance);
|
|
2847
|
+
const centralMassSolar = massInSolar(parentObject?.properties.mass);
|
|
2848
|
+
if (semiMajorAu === null || centralMassSolar === null || centralMassSolar <= 0) {
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
const periodYears = Math.sqrt(semiMajorAu ** 3 / centralMassSolar);
|
|
2852
|
+
return periodYears * 365.25;
|
|
2853
|
+
}
|
|
2854
|
+
function distanceInAu(value) {
|
|
2855
|
+
if (!value)
|
|
2856
|
+
return null;
|
|
2857
|
+
switch (value.unit) {
|
|
2858
|
+
case null:
|
|
2859
|
+
case "au":
|
|
2860
|
+
return value.value;
|
|
2861
|
+
case "km":
|
|
2862
|
+
return value.value / AU_IN_KM2;
|
|
2863
|
+
case "m":
|
|
2864
|
+
return value.value / (AU_IN_KM2 * 1e3);
|
|
2865
|
+
case "ly":
|
|
2866
|
+
return value.value * LY_IN_AU2;
|
|
2867
|
+
case "pc":
|
|
2868
|
+
return value.value * PC_IN_AU2;
|
|
2869
|
+
case "kpc":
|
|
2870
|
+
return value.value * KPC_IN_AU2;
|
|
2871
|
+
case "re":
|
|
2872
|
+
return value.value * EARTH_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
2873
|
+
case "sol":
|
|
2874
|
+
return value.value * SOLAR_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
2875
|
+
default:
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
function massInSolar(value) {
|
|
2880
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
const unitValue = value;
|
|
2884
|
+
switch (unitValue.unit) {
|
|
2885
|
+
case null:
|
|
2886
|
+
case "sol":
|
|
2887
|
+
return unitValue.value;
|
|
2888
|
+
case "me":
|
|
2889
|
+
return unitValue.value / EARTH_MASSES_PER_SOLAR;
|
|
2890
|
+
case "mj":
|
|
2891
|
+
return unitValue.value / JUPITER_MASSES_PER_SOLAR;
|
|
2892
|
+
default:
|
|
2893
|
+
return null;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
function durationInDays(value) {
|
|
2897
|
+
if (!value)
|
|
2898
|
+
return null;
|
|
2899
|
+
switch (value.unit) {
|
|
2900
|
+
case null:
|
|
2901
|
+
case "d":
|
|
2902
|
+
return value.value;
|
|
2903
|
+
case "s":
|
|
2904
|
+
return value.value / 86400;
|
|
2905
|
+
case "min":
|
|
2906
|
+
return value.value / 1440;
|
|
2907
|
+
case "h":
|
|
2908
|
+
return value.value / 24;
|
|
2909
|
+
case "y":
|
|
2910
|
+
return value.value * 365.25;
|
|
2911
|
+
case "ky":
|
|
2912
|
+
return value.value * 365250;
|
|
2913
|
+
case "my":
|
|
2914
|
+
return value.value * 36525e4;
|
|
2915
|
+
case "gy":
|
|
2916
|
+
return value.value * 36525e7;
|
|
2917
|
+
default:
|
|
2918
|
+
return null;
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
function toleranceForField(object, field) {
|
|
2922
|
+
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
2923
|
+
if (typeof tolerance === "number") {
|
|
2924
|
+
return tolerance;
|
|
2925
|
+
}
|
|
2926
|
+
if (tolerance && typeof tolerance === "object" && "value" in tolerance) {
|
|
2927
|
+
return durationInDays(tolerance) ?? 0;
|
|
2928
|
+
}
|
|
2929
|
+
return 0;
|
|
2930
|
+
}
|
|
2931
|
+
function formatDays(days) {
|
|
2932
|
+
return `${Math.round(days * 100) / 100}d`;
|
|
2933
|
+
}
|
|
2934
|
+
function error(code, message, objectId, field) {
|
|
2935
|
+
return { code, severity: "error", source: "validate", message, objectId, field };
|
|
2936
|
+
}
|
|
2937
|
+
function warn(code, message, objectId, field) {
|
|
2938
|
+
return { code, severity: "warning", source: "validate", message, objectId, field };
|
|
2939
|
+
}
|
|
2940
|
+
function info(code, message, objectId, field) {
|
|
2941
|
+
return { code, severity: "info", source: "validate", message, objectId, field };
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2407
2944
|
// packages/core/dist/draft-parse.js
|
|
2945
|
+
var STRUCTURED_TYPED_BLOCKS = /* @__PURE__ */ new Set([
|
|
2946
|
+
"climate",
|
|
2947
|
+
"habitability",
|
|
2948
|
+
"settlement"
|
|
2949
|
+
]);
|
|
2950
|
+
var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
|
|
2951
|
+
for (const key of [
|
|
2952
|
+
"orbit",
|
|
2953
|
+
"distance",
|
|
2954
|
+
"semiMajor",
|
|
2955
|
+
"eccentricity",
|
|
2956
|
+
"period",
|
|
2957
|
+
"angle",
|
|
2958
|
+
"inclination",
|
|
2959
|
+
"phase",
|
|
2960
|
+
"at",
|
|
2961
|
+
"surface",
|
|
2962
|
+
"free",
|
|
2963
|
+
"kind",
|
|
2964
|
+
"class",
|
|
2965
|
+
"culture",
|
|
2966
|
+
"tags",
|
|
2967
|
+
"color",
|
|
2968
|
+
"image",
|
|
2969
|
+
"hidden",
|
|
2970
|
+
"radius",
|
|
2971
|
+
"mass",
|
|
2972
|
+
"density",
|
|
2973
|
+
"gravity",
|
|
2974
|
+
"temperature",
|
|
2975
|
+
"albedo",
|
|
2976
|
+
"atmosphere",
|
|
2977
|
+
"inner",
|
|
2978
|
+
"outer",
|
|
2979
|
+
"on",
|
|
2980
|
+
"source",
|
|
2981
|
+
"cycle"
|
|
2982
|
+
]) {
|
|
2983
|
+
const schema = getFieldSchema(key);
|
|
2984
|
+
if (schema) {
|
|
2985
|
+
DRAFT_OBJECT_FIELD_SPECS.set(key, {
|
|
2986
|
+
key,
|
|
2987
|
+
version: "2.0",
|
|
2988
|
+
inlineMode: schema.arity === "multiple" ? "multiple" : "single",
|
|
2989
|
+
allowRepeat: false,
|
|
2990
|
+
legacySchema: schema
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
for (const spec of [
|
|
2995
|
+
{ key: "groups", inlineMode: "multiple", allowRepeat: false },
|
|
2996
|
+
{ key: "epoch", inlineMode: "single", allowRepeat: false },
|
|
2997
|
+
{ key: "referencePlane", inlineMode: "single", allowRepeat: false },
|
|
2998
|
+
{ key: "tidalLock", inlineMode: "single", allowRepeat: false },
|
|
2999
|
+
{ key: "renderLabel", inlineMode: "single", allowRepeat: false },
|
|
3000
|
+
{ key: "renderOrbit", inlineMode: "single", allowRepeat: false },
|
|
3001
|
+
{ key: "renderPriority", inlineMode: "single", allowRepeat: false },
|
|
3002
|
+
{ key: "resonance", inlineMode: "pair", allowRepeat: false },
|
|
3003
|
+
{ key: "derive", inlineMode: "pair", allowRepeat: true },
|
|
3004
|
+
{ key: "validate", inlineMode: "single", allowRepeat: true },
|
|
3005
|
+
{ key: "locked", inlineMode: "multiple", allowRepeat: false },
|
|
3006
|
+
{ key: "tolerance", inlineMode: "pair", allowRepeat: true }
|
|
3007
|
+
]) {
|
|
3008
|
+
DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
|
|
3009
|
+
key: spec.key,
|
|
3010
|
+
version: "2.1",
|
|
3011
|
+
inlineMode: spec.inlineMode,
|
|
3012
|
+
allowRepeat: spec.allowRepeat
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
2408
3016
|
function parseWorldOrbitAtlas(source) {
|
|
2409
|
-
return parseAtlasSource(source
|
|
3017
|
+
return parseAtlasSource(source);
|
|
2410
3018
|
}
|
|
2411
|
-
function parseAtlasSource(source,
|
|
2412
|
-
const
|
|
3019
|
+
function parseAtlasSource(source, forcedOutputVersion) {
|
|
3020
|
+
const prepared = preprocessAtlasSource(source);
|
|
3021
|
+
const lines = prepared.source.split(/\r?\n/);
|
|
3022
|
+
const diagnostics = [];
|
|
2413
3023
|
let sawSchemaHeader = false;
|
|
2414
|
-
let
|
|
3024
|
+
let sourceSchemaVersion = "2.0";
|
|
2415
3025
|
let system = null;
|
|
2416
3026
|
let section = null;
|
|
2417
3027
|
const objectNodes = [];
|
|
3028
|
+
const groups = [];
|
|
3029
|
+
const relations = [];
|
|
2418
3030
|
let sawDefaults = false;
|
|
2419
3031
|
let sawAtlas = false;
|
|
2420
3032
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
2421
3033
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3034
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
3035
|
+
const relationIds = /* @__PURE__ */ new Set();
|
|
2422
3036
|
for (let index = 0; index < lines.length; index++) {
|
|
2423
3037
|
const rawLine = lines[index];
|
|
2424
3038
|
const lineNumber = index + 1;
|
|
@@ -2434,15 +3048,22 @@
|
|
|
2434
3048
|
continue;
|
|
2435
3049
|
}
|
|
2436
3050
|
if (!sawSchemaHeader) {
|
|
2437
|
-
|
|
3051
|
+
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
2438
3052
|
sawSchemaHeader = true;
|
|
3053
|
+
if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
|
|
3054
|
+
diagnostics.push({
|
|
3055
|
+
code: "parse.schema21.commentCompatibility",
|
|
3056
|
+
severity: "warning",
|
|
3057
|
+
source: "parse",
|
|
3058
|
+
message: `Comments require schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
3059
|
+
line: prepared.comments[0].line,
|
|
3060
|
+
column: prepared.comments[0].column
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
2439
3063
|
continue;
|
|
2440
3064
|
}
|
|
2441
3065
|
if (indent === 0) {
|
|
2442
|
-
section = startTopLevelSection(tokens, lineNumber, system, objectNodes, viewpointIds, annotationIds, {
|
|
2443
|
-
sawDefaults,
|
|
2444
|
-
sawAtlas
|
|
2445
|
-
});
|
|
3066
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
2446
3067
|
if (section.kind === "system") {
|
|
2447
3068
|
system = section.system;
|
|
2448
3069
|
} else if (section.kind === "defaults") {
|
|
@@ -2460,48 +3081,57 @@
|
|
|
2460
3081
|
if (!sawSchemaHeader) {
|
|
2461
3082
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
2462
3083
|
}
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
};
|
|
2467
|
-
const normalizedObjects = normalizeDocument(ast).objects;
|
|
2468
|
-
validateDocument({
|
|
2469
|
-
format: "worldorbit",
|
|
2470
|
-
version: "1.0",
|
|
2471
|
-
system: null,
|
|
2472
|
-
objects: normalizedObjects
|
|
2473
|
-
});
|
|
2474
|
-
const diagnostics = schemaVersion === "2.0-draft" && outputVersion === "2.0" ? [
|
|
2475
|
-
{
|
|
2476
|
-
code: "load.schema.deprecatedDraft",
|
|
2477
|
-
severity: "warning",
|
|
2478
|
-
source: "upgrade",
|
|
2479
|
-
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
2480
|
-
}
|
|
2481
|
-
] : [];
|
|
2482
|
-
return {
|
|
3084
|
+
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
3085
|
+
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3086
|
+
const baseDocument = {
|
|
2483
3087
|
format: "worldorbit",
|
|
2484
|
-
version: outputVersion,
|
|
2485
3088
|
sourceVersion: "1.0",
|
|
2486
3089
|
system,
|
|
2487
|
-
|
|
3090
|
+
groups,
|
|
3091
|
+
relations,
|
|
3092
|
+
objects,
|
|
2488
3093
|
diagnostics
|
|
2489
3094
|
};
|
|
2490
|
-
|
|
3095
|
+
if (outputVersion === "2.0-draft") {
|
|
3096
|
+
const document3 = {
|
|
3097
|
+
...baseDocument,
|
|
3098
|
+
version: "2.0-draft",
|
|
3099
|
+
schemaVersion: "2.0-draft"
|
|
3100
|
+
};
|
|
3101
|
+
document3.diagnostics.push(...collectAtlasDiagnostics(document3, sourceSchemaVersion));
|
|
3102
|
+
return document3;
|
|
3103
|
+
}
|
|
3104
|
+
const document2 = {
|
|
3105
|
+
...baseDocument,
|
|
3106
|
+
version: outputVersion,
|
|
3107
|
+
schemaVersion: outputVersion
|
|
3108
|
+
};
|
|
3109
|
+
if (sourceSchemaVersion === "2.0-draft") {
|
|
3110
|
+
document2.diagnostics.push({
|
|
3111
|
+
code: "load.schema.deprecatedDraft",
|
|
3112
|
+
severity: "warning",
|
|
3113
|
+
source: "upgrade",
|
|
3114
|
+
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
document2.diagnostics.push(...collectAtlasDiagnostics(document2, sourceSchemaVersion));
|
|
3118
|
+
return document2;
|
|
3119
|
+
}
|
|
2491
3120
|
function assertDraftSchemaHeader(tokens, line) {
|
|
2492
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" ||
|
|
2493
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0" or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3121
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3122
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
2494
3123
|
}
|
|
2495
|
-
|
|
3124
|
+
const version = tokens[1].value.toLowerCase();
|
|
3125
|
+
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
2496
3126
|
}
|
|
2497
|
-
function startTopLevelSection(tokens, line, system, objectNodes, viewpointIds, annotationIds, flags) {
|
|
3127
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
2498
3128
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
2499
3129
|
switch (keyword) {
|
|
2500
3130
|
case "system":
|
|
2501
3131
|
if (system) {
|
|
2502
3132
|
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
2503
3133
|
}
|
|
2504
|
-
return startSystemSection(tokens, line);
|
|
3134
|
+
return startSystemSection(tokens, line, sourceSchemaVersion, diagnostics);
|
|
2505
3135
|
case "defaults":
|
|
2506
3136
|
if (!system) {
|
|
2507
3137
|
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -2537,13 +3167,19 @@
|
|
|
2537
3167
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
2538
3168
|
}
|
|
2539
3169
|
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
3170
|
+
case "group":
|
|
3171
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "group", { line, column: tokens[0].column });
|
|
3172
|
+
return startGroupSection(tokens, line, groups, groupIds);
|
|
3173
|
+
case "relation":
|
|
3174
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3175
|
+
return startRelationSection(tokens, line, relations, relationIds);
|
|
2540
3176
|
case "object":
|
|
2541
|
-
return startObjectSection(tokens, line, objectNodes);
|
|
3177
|
+
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
2542
3178
|
default:
|
|
2543
3179
|
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
2544
3180
|
}
|
|
2545
3181
|
}
|
|
2546
|
-
function startSystemSection(tokens, line) {
|
|
3182
|
+
function startSystemSection(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
2547
3183
|
if (tokens.length !== 2) {
|
|
2548
3184
|
throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
|
|
2549
3185
|
}
|
|
@@ -2551,6 +3187,9 @@
|
|
|
2551
3187
|
type: "system",
|
|
2552
3188
|
id: tokens[1].value,
|
|
2553
3189
|
title: null,
|
|
3190
|
+
description: null,
|
|
3191
|
+
epoch: null,
|
|
3192
|
+
referencePlane: null,
|
|
2554
3193
|
defaults: {
|
|
2555
3194
|
view: "topdown",
|
|
2556
3195
|
scale: null,
|
|
@@ -2565,6 +3204,8 @@
|
|
|
2565
3204
|
return {
|
|
2566
3205
|
kind: "system",
|
|
2567
3206
|
system,
|
|
3207
|
+
sourceSchemaVersion,
|
|
3208
|
+
diagnostics,
|
|
2568
3209
|
seenFields: /* @__PURE__ */ new Set()
|
|
2569
3210
|
};
|
|
2570
3211
|
}
|
|
@@ -2630,7 +3271,64 @@
|
|
|
2630
3271
|
seenFields: /* @__PURE__ */ new Set()
|
|
2631
3272
|
};
|
|
2632
3273
|
}
|
|
2633
|
-
function
|
|
3274
|
+
function startGroupSection(tokens, line, groups, groupIds) {
|
|
3275
|
+
if (tokens.length !== 2) {
|
|
3276
|
+
throw new WorldOrbitError("Invalid group declaration", line, tokens[0]?.column ?? 1);
|
|
3277
|
+
}
|
|
3278
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3279
|
+
if (!id) {
|
|
3280
|
+
throw new WorldOrbitError("Group id must not be empty", line, tokens[1].column);
|
|
3281
|
+
}
|
|
3282
|
+
if (groupIds.has(id)) {
|
|
3283
|
+
throw new WorldOrbitError(`Duplicate group id "${id}"`, line, tokens[1].column);
|
|
3284
|
+
}
|
|
3285
|
+
const group = {
|
|
3286
|
+
id,
|
|
3287
|
+
label: humanizeIdentifier2(id),
|
|
3288
|
+
summary: "",
|
|
3289
|
+
color: null,
|
|
3290
|
+
tags: [],
|
|
3291
|
+
hidden: false
|
|
3292
|
+
};
|
|
3293
|
+
groups.push(group);
|
|
3294
|
+
groupIds.add(id);
|
|
3295
|
+
return {
|
|
3296
|
+
kind: "group",
|
|
3297
|
+
group,
|
|
3298
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
function startRelationSection(tokens, line, relations, relationIds) {
|
|
3302
|
+
if (tokens.length !== 2) {
|
|
3303
|
+
throw new WorldOrbitError("Invalid relation declaration", line, tokens[0]?.column ?? 1);
|
|
3304
|
+
}
|
|
3305
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3306
|
+
if (!id) {
|
|
3307
|
+
throw new WorldOrbitError("Relation id must not be empty", line, tokens[1].column);
|
|
3308
|
+
}
|
|
3309
|
+
if (relationIds.has(id)) {
|
|
3310
|
+
throw new WorldOrbitError(`Duplicate relation id "${id}"`, line, tokens[1].column);
|
|
3311
|
+
}
|
|
3312
|
+
const relation = {
|
|
3313
|
+
id,
|
|
3314
|
+
from: "",
|
|
3315
|
+
to: "",
|
|
3316
|
+
kind: "",
|
|
3317
|
+
label: null,
|
|
3318
|
+
summary: null,
|
|
3319
|
+
tags: [],
|
|
3320
|
+
color: null,
|
|
3321
|
+
hidden: false
|
|
3322
|
+
};
|
|
3323
|
+
relations.push(relation);
|
|
3324
|
+
relationIds.add(id);
|
|
3325
|
+
return {
|
|
3326
|
+
kind: "relation",
|
|
3327
|
+
relation,
|
|
3328
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
2634
3332
|
if (tokens.length < 3) {
|
|
2635
3333
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
2636
3334
|
}
|
|
@@ -2641,12 +3339,11 @@
|
|
|
2641
3339
|
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
2642
3340
|
}
|
|
2643
3341
|
const objectNode = {
|
|
2644
|
-
type: "object",
|
|
2645
3342
|
objectType,
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
blockFields: [],
|
|
3343
|
+
id: idToken.value,
|
|
3344
|
+
fields: parseInlineObjectFields(tokens.slice(3), line, objectType, sourceSchemaVersion, diagnostics),
|
|
2649
3345
|
infoEntries: [],
|
|
3346
|
+
typedBlockEntries: {},
|
|
2650
3347
|
location: {
|
|
2651
3348
|
line,
|
|
2652
3349
|
column: objectTypeToken.column
|
|
@@ -2656,8 +3353,12 @@
|
|
|
2656
3353
|
return {
|
|
2657
3354
|
kind: "object",
|
|
2658
3355
|
objectNode,
|
|
2659
|
-
|
|
2660
|
-
|
|
3356
|
+
sourceSchemaVersion,
|
|
3357
|
+
diagnostics,
|
|
3358
|
+
activeBlock: null,
|
|
3359
|
+
blockIndent: null,
|
|
3360
|
+
seenInfoKeys: /* @__PURE__ */ new Set(),
|
|
3361
|
+
seenTypedBlockKeys: {}
|
|
2661
3362
|
};
|
|
2662
3363
|
}
|
|
2663
3364
|
function handleSectionLine(section, indent, tokens, line) {
|
|
@@ -2677,6 +3378,12 @@
|
|
|
2677
3378
|
case "annotation":
|
|
2678
3379
|
applyAnnotationField(section, tokens, line);
|
|
2679
3380
|
return;
|
|
3381
|
+
case "group":
|
|
3382
|
+
applyGroupField(section, tokens, line);
|
|
3383
|
+
return;
|
|
3384
|
+
case "relation":
|
|
3385
|
+
applyRelationField(section, tokens, line);
|
|
3386
|
+
return;
|
|
2680
3387
|
case "object":
|
|
2681
3388
|
applyObjectField(section, indent, tokens, line);
|
|
2682
3389
|
return;
|
|
@@ -2684,10 +3391,35 @@
|
|
|
2684
3391
|
}
|
|
2685
3392
|
function applySystemField(section, tokens, line) {
|
|
2686
3393
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
2687
|
-
|
|
2688
|
-
|
|
3394
|
+
const value = joinFieldValue(tokens, line);
|
|
3395
|
+
switch (key) {
|
|
3396
|
+
case "title":
|
|
3397
|
+
section.system.title = value;
|
|
3398
|
+
return;
|
|
3399
|
+
case "description":
|
|
3400
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
3401
|
+
line,
|
|
3402
|
+
column: tokens[0].column
|
|
3403
|
+
});
|
|
3404
|
+
section.system.description = value;
|
|
3405
|
+
return;
|
|
3406
|
+
case "epoch":
|
|
3407
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
3408
|
+
line,
|
|
3409
|
+
column: tokens[0].column
|
|
3410
|
+
});
|
|
3411
|
+
section.system.epoch = value;
|
|
3412
|
+
return;
|
|
3413
|
+
case "referenceplane":
|
|
3414
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "referencePlane", {
|
|
3415
|
+
line,
|
|
3416
|
+
column: tokens[0].column
|
|
3417
|
+
});
|
|
3418
|
+
section.system.referencePlane = value;
|
|
3419
|
+
return;
|
|
3420
|
+
default:
|
|
3421
|
+
throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
2689
3422
|
}
|
|
2690
|
-
section.system.title = joinFieldValue(tokens, line);
|
|
2691
3423
|
}
|
|
2692
3424
|
function applyDefaultsField(section, tokens, line) {
|
|
2693
3425
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
@@ -2718,14 +3450,11 @@
|
|
|
2718
3450
|
section.metadataIndent = null;
|
|
2719
3451
|
}
|
|
2720
3452
|
if (section.inMetadata) {
|
|
2721
|
-
|
|
2722
|
-
|
|
3453
|
+
const entry = parseInfoLikeEntry(tokens, line, "Invalid atlas metadata entry");
|
|
3454
|
+
if (entry.key in section.system.atlasMetadata) {
|
|
3455
|
+
throw new WorldOrbitError(`Duplicate atlas metadata key "${entry.key}"`, line, tokens[0].column);
|
|
2723
3456
|
}
|
|
2724
|
-
|
|
2725
|
-
if (key in section.system.atlasMetadata) {
|
|
2726
|
-
throw new WorldOrbitError(`Duplicate atlas metadata key "${key}"`, line, tokens[0].column);
|
|
2727
|
-
}
|
|
2728
|
-
section.system.atlasMetadata[key] = joinFieldValue(tokens, line);
|
|
3457
|
+
section.system.atlasMetadata[entry.key] = entry.value;
|
|
2729
3458
|
return;
|
|
2730
3459
|
}
|
|
2731
3460
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
|
|
@@ -2827,21 +3556,102 @@
|
|
|
2827
3556
|
throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
2828
3557
|
}
|
|
2829
3558
|
}
|
|
2830
|
-
function
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3559
|
+
function applyGroupField(section, tokens, line) {
|
|
3560
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3561
|
+
switch (key) {
|
|
3562
|
+
case "label":
|
|
3563
|
+
section.group.label = joinFieldValue(tokens, line);
|
|
3564
|
+
return;
|
|
3565
|
+
case "summary":
|
|
3566
|
+
section.group.summary = joinFieldValue(tokens, line);
|
|
3567
|
+
return;
|
|
3568
|
+
case "color":
|
|
3569
|
+
section.group.color = joinFieldValue(tokens, line);
|
|
3570
|
+
return;
|
|
3571
|
+
case "tags":
|
|
3572
|
+
section.group.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3573
|
+
return;
|
|
3574
|
+
case "hidden":
|
|
3575
|
+
section.group.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
3576
|
+
line,
|
|
3577
|
+
column: tokens[0].column
|
|
3578
|
+
});
|
|
3579
|
+
return;
|
|
3580
|
+
default:
|
|
3581
|
+
throw new WorldOrbitError(`Unknown group field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
function applyRelationField(section, tokens, line) {
|
|
3585
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3586
|
+
switch (key) {
|
|
3587
|
+
case "from":
|
|
3588
|
+
section.relation.from = joinFieldValue(tokens, line);
|
|
3589
|
+
return;
|
|
3590
|
+
case "to":
|
|
3591
|
+
section.relation.to = joinFieldValue(tokens, line);
|
|
3592
|
+
return;
|
|
3593
|
+
case "kind":
|
|
3594
|
+
section.relation.kind = joinFieldValue(tokens, line);
|
|
3595
|
+
return;
|
|
3596
|
+
case "label":
|
|
3597
|
+
section.relation.label = joinFieldValue(tokens, line);
|
|
3598
|
+
return;
|
|
3599
|
+
case "summary":
|
|
3600
|
+
section.relation.summary = joinFieldValue(tokens, line);
|
|
3601
|
+
return;
|
|
3602
|
+
case "tags":
|
|
3603
|
+
section.relation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3604
|
+
return;
|
|
3605
|
+
case "color":
|
|
3606
|
+
section.relation.color = joinFieldValue(tokens, line);
|
|
3607
|
+
return;
|
|
3608
|
+
case "hidden":
|
|
3609
|
+
section.relation.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
3610
|
+
line,
|
|
3611
|
+
column: tokens[0].column
|
|
3612
|
+
});
|
|
3613
|
+
return;
|
|
3614
|
+
default:
|
|
3615
|
+
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
2835
3616
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3617
|
+
}
|
|
3618
|
+
function applyObjectField(section, indent, tokens, line) {
|
|
3619
|
+
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
3620
|
+
section.activeBlock = null;
|
|
3621
|
+
section.blockIndent = null;
|
|
3622
|
+
}
|
|
3623
|
+
if (tokens.length === 1) {
|
|
3624
|
+
const blockName = tokens[0].value.toLowerCase();
|
|
3625
|
+
if (blockName === "info" || STRUCTURED_TYPED_BLOCKS.has(blockName)) {
|
|
3626
|
+
if (blockName !== "info") {
|
|
3627
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, blockName, { line, column: tokens[0].column });
|
|
3628
|
+
}
|
|
3629
|
+
section.activeBlock = blockName;
|
|
3630
|
+
section.blockIndent = indent;
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
2839
3633
|
}
|
|
2840
|
-
if (section.
|
|
2841
|
-
|
|
3634
|
+
if (section.activeBlock) {
|
|
3635
|
+
const entry = parseInfoLikeEntry(tokens, line, `Invalid ${section.activeBlock} entry`);
|
|
3636
|
+
if (section.activeBlock === "info") {
|
|
3637
|
+
if (section.seenInfoKeys.has(entry.key)) {
|
|
3638
|
+
throw new WorldOrbitError(`Duplicate info key "${entry.key}"`, line, tokens[0].column);
|
|
3639
|
+
}
|
|
3640
|
+
section.seenInfoKeys.add(entry.key);
|
|
3641
|
+
section.objectNode.infoEntries.push(entry);
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const typedBlock = section.activeBlock;
|
|
3645
|
+
const seenKeys = section.seenTypedBlockKeys[typedBlock] ?? (section.seenTypedBlockKeys[typedBlock] = /* @__PURE__ */ new Set());
|
|
3646
|
+
if (seenKeys.has(entry.key)) {
|
|
3647
|
+
throw new WorldOrbitError(`Duplicate ${typedBlock} key "${entry.key}"`, line, tokens[0].column);
|
|
3648
|
+
}
|
|
3649
|
+
seenKeys.add(entry.key);
|
|
3650
|
+
const entries = section.objectNode.typedBlockEntries[typedBlock] ?? (section.objectNode.typedBlockEntries[typedBlock] = []);
|
|
3651
|
+
entries.push(entry);
|
|
2842
3652
|
return;
|
|
2843
3653
|
}
|
|
2844
|
-
section.objectNode.
|
|
3654
|
+
section.objectNode.fields.push(parseObjectField(tokens, line, section.objectNode.objectType, section.sourceSchemaVersion, section.diagnostics));
|
|
2845
3655
|
}
|
|
2846
3656
|
function requireUniqueField(tokens, seenFields, line) {
|
|
2847
3657
|
if (tokens.length < 2) {
|
|
@@ -2861,50 +3671,40 @@
|
|
|
2861
3671
|
return tokens.slice(1).map((token) => token.value).join(" ").trim();
|
|
2862
3672
|
}
|
|
2863
3673
|
function parseObjectTypeTokens(tokens, line) {
|
|
2864
|
-
|
|
2865
|
-
throw new WorldOrbitError("Missing value for atlas field", line);
|
|
2866
|
-
}
|
|
2867
|
-
return tokens.map((token) => {
|
|
2868
|
-
const value = token.value;
|
|
2869
|
-
if (value !== "star" && value !== "planet" && value !== "moon" && value !== "belt" && value !== "asteroid" && value !== "comet" && value !== "ring" && value !== "structure" && value !== "phenomenon") {
|
|
2870
|
-
throw new WorldOrbitError(`Unknown viewpoint object type "${token.value}"`, line, token.column);
|
|
2871
|
-
}
|
|
2872
|
-
return value;
|
|
2873
|
-
});
|
|
2874
|
-
}
|
|
2875
|
-
function parseTokenList(tokens, line, field) {
|
|
2876
|
-
if (tokens.length === 0) {
|
|
2877
|
-
throw new WorldOrbitError(`Missing value for field "${field}"`, line);
|
|
2878
|
-
}
|
|
2879
|
-
return tokens.map((token) => token.value);
|
|
3674
|
+
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");
|
|
2880
3675
|
}
|
|
2881
3676
|
function parseLayerTokens(tokens, line) {
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
if (rawLayer === "orbits") {
|
|
2890
|
-
next["orbits-back"] = enabled;
|
|
2891
|
-
next["orbits-front"] = enabled;
|
|
3677
|
+
const layers = {};
|
|
3678
|
+
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
3679
|
+
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
3680
|
+
const raw = token.replace(/^[-!]+/, "").toLowerCase();
|
|
3681
|
+
if (raw === "orbits") {
|
|
3682
|
+
layers["orbits-back"] = enabled;
|
|
3683
|
+
layers["orbits-front"] = enabled;
|
|
2892
3684
|
continue;
|
|
2893
3685
|
}
|
|
2894
|
-
if (
|
|
2895
|
-
|
|
2896
|
-
continue;
|
|
3686
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
3687
|
+
layers[raw] = enabled;
|
|
2897
3688
|
}
|
|
2898
|
-
throw new WorldOrbitError(`Unknown layer token "${token.value}"`, line, token.column);
|
|
2899
3689
|
}
|
|
2900
|
-
return
|
|
3690
|
+
return layers;
|
|
3691
|
+
}
|
|
3692
|
+
function parseTokenList(tokens, line, fieldName) {
|
|
3693
|
+
if (tokens.length === 0) {
|
|
3694
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, 1);
|
|
3695
|
+
}
|
|
3696
|
+
const values = tokens.map((token) => token.value).filter(Boolean);
|
|
3697
|
+
if (values.length === 0) {
|
|
3698
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, tokens[0]?.column ?? 1);
|
|
3699
|
+
}
|
|
3700
|
+
return values;
|
|
2901
3701
|
}
|
|
2902
3702
|
function parseProjectionValue(value, line, column) {
|
|
2903
3703
|
const normalized = value.toLowerCase();
|
|
2904
|
-
if (normalized
|
|
2905
|
-
|
|
3704
|
+
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
3705
|
+
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
2906
3706
|
}
|
|
2907
|
-
|
|
3707
|
+
return normalized;
|
|
2908
3708
|
}
|
|
2909
3709
|
function parsePresetValue(value, line, column) {
|
|
2910
3710
|
const normalized = value.toLowerCase();
|
|
@@ -2914,16 +3714,16 @@
|
|
|
2914
3714
|
throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
|
|
2915
3715
|
}
|
|
2916
3716
|
function parsePositiveNumber2(value, line, column, field) {
|
|
2917
|
-
const parsed =
|
|
2918
|
-
if (
|
|
2919
|
-
throw new WorldOrbitError(`Field "${field}"
|
|
3717
|
+
const parsed = parseFiniteNumber2(value, line, column, field);
|
|
3718
|
+
if (parsed <= 0) {
|
|
3719
|
+
throw new WorldOrbitError(`Field "${field}" must be greater than zero`, line, column);
|
|
2920
3720
|
}
|
|
2921
3721
|
return parsed;
|
|
2922
3722
|
}
|
|
2923
3723
|
function parseFiniteNumber2(value, line, column, field) {
|
|
2924
3724
|
const parsed = Number(value);
|
|
2925
3725
|
if (!Number.isFinite(parsed)) {
|
|
2926
|
-
throw new WorldOrbitError(`
|
|
3726
|
+
throw new WorldOrbitError(`Invalid numeric value "${value}" for "${field}"`, line, column);
|
|
2927
3727
|
}
|
|
2928
3728
|
return parsed;
|
|
2929
3729
|
}
|
|
@@ -2935,28 +3735,43 @@
|
|
|
2935
3735
|
groupIds: []
|
|
2936
3736
|
};
|
|
2937
3737
|
}
|
|
2938
|
-
function
|
|
3738
|
+
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
2939
3739
|
const fields = [];
|
|
2940
3740
|
let index = 0;
|
|
2941
3741
|
while (index < tokens.length) {
|
|
2942
3742
|
const keyToken = tokens[index];
|
|
2943
|
-
const
|
|
2944
|
-
if (!
|
|
3743
|
+
const spec = getDraftObjectFieldSpec(keyToken.value);
|
|
3744
|
+
if (!spec) {
|
|
2945
3745
|
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
2946
3746
|
}
|
|
3747
|
+
if (spec.version === "2.1") {
|
|
3748
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
|
|
3749
|
+
line,
|
|
3750
|
+
column: keyToken.column
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
2947
3753
|
index++;
|
|
2948
3754
|
const valueTokens = [];
|
|
2949
|
-
if (
|
|
2950
|
-
while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
|
|
2951
|
-
valueTokens.push(tokens[index]);
|
|
2952
|
-
index++;
|
|
2953
|
-
}
|
|
2954
|
-
} else {
|
|
3755
|
+
if (spec.inlineMode === "single") {
|
|
2955
3756
|
const nextToken = tokens[index];
|
|
2956
3757
|
if (nextToken) {
|
|
2957
3758
|
valueTokens.push(nextToken);
|
|
2958
3759
|
index++;
|
|
2959
3760
|
}
|
|
3761
|
+
} else if (spec.inlineMode === "pair") {
|
|
3762
|
+
for (let count = 0; count < 2; count++) {
|
|
3763
|
+
const nextToken = tokens[index];
|
|
3764
|
+
if (!nextToken) {
|
|
3765
|
+
break;
|
|
3766
|
+
}
|
|
3767
|
+
valueTokens.push(nextToken);
|
|
3768
|
+
index++;
|
|
3769
|
+
}
|
|
3770
|
+
} else {
|
|
3771
|
+
while (index < tokens.length && !DRAFT_OBJECT_FIELD_KEYS.has(tokens[index].value)) {
|
|
3772
|
+
valueTokens.push(tokens[index]);
|
|
3773
|
+
index++;
|
|
3774
|
+
}
|
|
2960
3775
|
}
|
|
2961
3776
|
if (valueTokens.length === 0) {
|
|
2962
3777
|
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
@@ -2968,25 +3783,35 @@
|
|
|
2968
3783
|
location: { line, column: keyToken.column }
|
|
2969
3784
|
});
|
|
2970
3785
|
}
|
|
3786
|
+
validateDraftObjectFieldCompatibility(fields, objectType);
|
|
2971
3787
|
return fields;
|
|
2972
3788
|
}
|
|
2973
|
-
function
|
|
3789
|
+
function parseObjectField(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
2974
3790
|
if (tokens.length < 2) {
|
|
2975
3791
|
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
2976
3792
|
}
|
|
2977
|
-
|
|
3793
|
+
const spec = getDraftObjectFieldSpec(tokens[0].value);
|
|
3794
|
+
if (!spec) {
|
|
2978
3795
|
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
2979
3796
|
}
|
|
2980
|
-
|
|
3797
|
+
if (spec.version === "2.1") {
|
|
3798
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
|
|
3799
|
+
line,
|
|
3800
|
+
column: tokens[0].column
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3803
|
+
const field = {
|
|
2981
3804
|
type: "field",
|
|
2982
3805
|
key: tokens[0].value,
|
|
2983
3806
|
values: tokens.slice(1).map((token) => token.value),
|
|
2984
3807
|
location: { line, column: tokens[0].column }
|
|
2985
3808
|
};
|
|
3809
|
+
validateDraftObjectFieldCompatibility([field], objectType);
|
|
3810
|
+
return field;
|
|
2986
3811
|
}
|
|
2987
|
-
function
|
|
3812
|
+
function parseInfoLikeEntry(tokens, line, errorMessage) {
|
|
2988
3813
|
if (tokens.length < 2) {
|
|
2989
|
-
throw new WorldOrbitError(
|
|
3814
|
+
throw new WorldOrbitError(errorMessage, line, tokens[0]?.column ?? 1);
|
|
2990
3815
|
}
|
|
2991
3816
|
return {
|
|
2992
3817
|
type: "info-entry",
|
|
@@ -2995,18 +3820,348 @@
|
|
|
2995
3820
|
location: { line, column: tokens[0].column }
|
|
2996
3821
|
};
|
|
2997
3822
|
}
|
|
2998
|
-
function
|
|
2999
|
-
|
|
3823
|
+
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
3824
|
+
const fieldMap = collectDraftFields(node.fields);
|
|
3825
|
+
const placement = extractDraftPlacement(node.objectType, fieldMap);
|
|
3826
|
+
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
3827
|
+
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
3828
|
+
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
3829
|
+
const referencePlane = parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0]);
|
|
3830
|
+
const tidalLock = fieldMap.has("tidalLock") ? parseAtlasBoolean(singleFieldValue2(fieldMap.get("tidalLock")[0]), "tidalLock", fieldMap.get("tidalLock")[0].location) : void 0;
|
|
3831
|
+
const resonance = fieldMap.has("resonance") ? parseResonanceField(fieldMap.get("resonance")[0]) : void 0;
|
|
3832
|
+
const renderHints = extractRenderHints(fieldMap);
|
|
3833
|
+
const deriveRules = fieldMap.get("derive")?.map((field) => parseDeriveField(field));
|
|
3834
|
+
const validationRules = fieldMap.get("validate")?.map((field) => ({
|
|
3835
|
+
rule: singleFieldValue2(field)
|
|
3836
|
+
}));
|
|
3837
|
+
const lockedFields = fieldMap.has("locked") ? [...new Set(fieldMap.get("locked").flatMap((field) => field.values))] : void 0;
|
|
3838
|
+
const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
|
|
3839
|
+
const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
|
|
3840
|
+
const info2 = normalizeInfoEntries(node.infoEntries, "info");
|
|
3841
|
+
const object = {
|
|
3842
|
+
type: node.objectType,
|
|
3843
|
+
id: node.id,
|
|
3844
|
+
properties,
|
|
3845
|
+
placement,
|
|
3846
|
+
info: info2
|
|
3847
|
+
};
|
|
3848
|
+
if (groups.length > 0)
|
|
3849
|
+
object.groups = groups;
|
|
3850
|
+
if (epoch)
|
|
3851
|
+
object.epoch = epoch;
|
|
3852
|
+
if (referencePlane)
|
|
3853
|
+
object.referencePlane = referencePlane;
|
|
3854
|
+
if (tidalLock !== void 0)
|
|
3855
|
+
object.tidalLock = tidalLock;
|
|
3856
|
+
if (resonance)
|
|
3857
|
+
object.resonance = resonance;
|
|
3858
|
+
if (renderHints)
|
|
3859
|
+
object.renderHints = renderHints;
|
|
3860
|
+
if (deriveRules?.length)
|
|
3861
|
+
object.deriveRules = deriveRules;
|
|
3862
|
+
if (validationRules?.length)
|
|
3863
|
+
object.validationRules = validationRules;
|
|
3864
|
+
if (lockedFields?.length)
|
|
3865
|
+
object.lockedFields = lockedFields;
|
|
3866
|
+
if (tolerances?.length)
|
|
3867
|
+
object.tolerances = tolerances;
|
|
3868
|
+
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
3869
|
+
object.typedBlocks = typedBlocks;
|
|
3870
|
+
if (sourceSchemaVersion !== "2.1") {
|
|
3871
|
+
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) {
|
|
3872
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
return object;
|
|
3000
3876
|
}
|
|
3001
|
-
function
|
|
3002
|
-
|
|
3877
|
+
function collectDraftFields(fields) {
|
|
3878
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3879
|
+
for (const field of fields) {
|
|
3880
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
3881
|
+
if (!spec) {
|
|
3882
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
3883
|
+
}
|
|
3884
|
+
if (!spec.allowRepeat && grouped.has(field.key)) {
|
|
3885
|
+
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
3886
|
+
}
|
|
3887
|
+
const existing = grouped.get(field.key) ?? [];
|
|
3888
|
+
existing.push(field);
|
|
3889
|
+
grouped.set(field.key, existing);
|
|
3890
|
+
}
|
|
3891
|
+
return grouped;
|
|
3892
|
+
}
|
|
3893
|
+
function extractDraftPlacement(objectType, fieldMap) {
|
|
3894
|
+
const orbitField = fieldMap.get("orbit")?.[0];
|
|
3895
|
+
const atField = fieldMap.get("at")?.[0];
|
|
3896
|
+
const surfaceField = fieldMap.get("surface")?.[0];
|
|
3897
|
+
const freeField = fieldMap.get("free")?.[0];
|
|
3898
|
+
const count = [orbitField, atField, surfaceField, freeField].filter(Boolean).length;
|
|
3899
|
+
if (count > 1) {
|
|
3900
|
+
const conflictingField = orbitField ?? atField ?? surfaceField ?? freeField;
|
|
3901
|
+
throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
|
|
3902
|
+
}
|
|
3903
|
+
if (orbitField) {
|
|
3904
|
+
return {
|
|
3905
|
+
mode: "orbit",
|
|
3906
|
+
target: singleFieldValue2(orbitField),
|
|
3907
|
+
distance: parseOptionalUnitField(fieldMap.get("distance")?.[0], "distance"),
|
|
3908
|
+
semiMajor: parseOptionalUnitField(fieldMap.get("semiMajor")?.[0], "semiMajor"),
|
|
3909
|
+
eccentricity: parseOptionalNumberField(fieldMap.get("eccentricity")?.[0], "eccentricity"),
|
|
3910
|
+
period: parseOptionalUnitField(fieldMap.get("period")?.[0], "period"),
|
|
3911
|
+
angle: parseOptionalUnitField(fieldMap.get("angle")?.[0], "angle"),
|
|
3912
|
+
inclination: parseOptionalUnitField(fieldMap.get("inclination")?.[0], "inclination"),
|
|
3913
|
+
phase: parseOptionalUnitField(fieldMap.get("phase")?.[0], "phase")
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
if (atField) {
|
|
3917
|
+
const target = singleFieldValue2(atField);
|
|
3918
|
+
return {
|
|
3919
|
+
mode: "at",
|
|
3920
|
+
target,
|
|
3921
|
+
reference: parseAtlasAtReference(target, atField.location)
|
|
3922
|
+
};
|
|
3923
|
+
}
|
|
3924
|
+
if (surfaceField) {
|
|
3925
|
+
return {
|
|
3926
|
+
mode: "surface",
|
|
3927
|
+
target: singleFieldValue2(surfaceField)
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
if (freeField) {
|
|
3931
|
+
const raw = singleFieldValue2(freeField);
|
|
3932
|
+
const distance = tryParseAtlasUnitValue(raw);
|
|
3933
|
+
return {
|
|
3934
|
+
mode: "free",
|
|
3935
|
+
distance: distance ?? void 0,
|
|
3936
|
+
descriptor: distance ? void 0 : raw
|
|
3937
|
+
};
|
|
3938
|
+
}
|
|
3939
|
+
return null;
|
|
3940
|
+
}
|
|
3941
|
+
function normalizeDraftProperties(objectType, fieldMap) {
|
|
3942
|
+
const properties = {};
|
|
3943
|
+
for (const [key, fields] of fieldMap.entries()) {
|
|
3944
|
+
const field = fields[0];
|
|
3945
|
+
const spec = getDraftObjectFieldSpec(key);
|
|
3946
|
+
if (!field || !spec?.legacySchema || spec.legacySchema.placement) {
|
|
3947
|
+
continue;
|
|
3948
|
+
}
|
|
3949
|
+
ensureAtlasFieldSupported(key, objectType, field.location);
|
|
3950
|
+
properties[key] = normalizeLegacyScalarValue(key, field.values, field.location);
|
|
3951
|
+
}
|
|
3952
|
+
return properties;
|
|
3953
|
+
}
|
|
3954
|
+
function normalizeInfoEntries(entries, label) {
|
|
3955
|
+
const normalized = {};
|
|
3956
|
+
for (const entry of entries) {
|
|
3957
|
+
if (entry.key in normalized) {
|
|
3958
|
+
throw WorldOrbitError.fromLocation(`Duplicate ${label} key "${entry.key}"`, entry.location);
|
|
3959
|
+
}
|
|
3960
|
+
normalized[entry.key] = entry.value;
|
|
3961
|
+
}
|
|
3962
|
+
return normalized;
|
|
3963
|
+
}
|
|
3964
|
+
function normalizeTypedBlocks(typedBlockEntries) {
|
|
3965
|
+
const typedBlocks = {};
|
|
3966
|
+
for (const blockName of Object.keys(typedBlockEntries)) {
|
|
3967
|
+
const entries = typedBlockEntries[blockName];
|
|
3968
|
+
if (entries?.length) {
|
|
3969
|
+
typedBlocks[blockName] = normalizeInfoEntries(entries, blockName);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
return typedBlocks;
|
|
3973
|
+
}
|
|
3974
|
+
function extractRenderHints(fieldMap) {
|
|
3975
|
+
const renderHints = {};
|
|
3976
|
+
const renderLabelField = fieldMap.get("renderLabel")?.[0];
|
|
3977
|
+
const renderOrbitField = fieldMap.get("renderOrbit")?.[0];
|
|
3978
|
+
const renderPriorityField = fieldMap.get("renderPriority")?.[0];
|
|
3979
|
+
if (renderLabelField) {
|
|
3980
|
+
renderHints.renderLabel = parseAtlasBoolean(singleFieldValue2(renderLabelField), "renderLabel", renderLabelField.location);
|
|
3981
|
+
}
|
|
3982
|
+
if (renderOrbitField) {
|
|
3983
|
+
renderHints.renderOrbit = parseAtlasBoolean(singleFieldValue2(renderOrbitField), "renderOrbit", renderOrbitField.location);
|
|
3984
|
+
}
|
|
3985
|
+
if (renderPriorityField) {
|
|
3986
|
+
renderHints.renderPriority = parseAtlasNumber(singleFieldValue2(renderPriorityField), "renderPriority", renderPriorityField.location);
|
|
3987
|
+
}
|
|
3988
|
+
return Object.keys(renderHints).length > 0 ? renderHints : void 0;
|
|
3989
|
+
}
|
|
3990
|
+
function parseResonanceField(field) {
|
|
3991
|
+
if (field.values.length !== 2) {
|
|
3992
|
+
throw WorldOrbitError.fromLocation('Field "resonance" expects "<targetObjectId> <ratio>"', field.location);
|
|
3993
|
+
}
|
|
3994
|
+
const ratio = field.values[1];
|
|
3995
|
+
if (!/^\d+:\d+$/.test(ratio)) {
|
|
3996
|
+
throw WorldOrbitError.fromLocation(`Invalid resonance ratio "${ratio}"`, field.location);
|
|
3997
|
+
}
|
|
3998
|
+
return {
|
|
3999
|
+
targetObjectId: field.values[0],
|
|
4000
|
+
ratio
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
function parseDeriveField(field) {
|
|
4004
|
+
if (field.values.length !== 2) {
|
|
4005
|
+
throw WorldOrbitError.fromLocation('Field "derive" expects "<field> <strategy>"', field.location);
|
|
4006
|
+
}
|
|
4007
|
+
return {
|
|
4008
|
+
field: field.values[0],
|
|
4009
|
+
strategy: field.values[1]
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
function parseToleranceField(field) {
|
|
4013
|
+
if (field.values.length !== 2) {
|
|
4014
|
+
throw WorldOrbitError.fromLocation('Field "tolerance" expects "<field> <value>"', field.location);
|
|
4015
|
+
}
|
|
4016
|
+
const rawValue = field.values[1];
|
|
4017
|
+
const unitValue = tryParseAtlasUnitValue(rawValue);
|
|
4018
|
+
const numericValue2 = Number(rawValue);
|
|
4019
|
+
return {
|
|
4020
|
+
field: field.values[0],
|
|
4021
|
+
value: unitValue ?? (Number.isFinite(numericValue2) ? numericValue2 : rawValue)
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
function parseOptionalTokenList(field) {
|
|
4025
|
+
return field ? [...new Set(field.values)] : [];
|
|
4026
|
+
}
|
|
4027
|
+
function parseOptionalJoinedValue(field) {
|
|
4028
|
+
if (!field) {
|
|
4029
|
+
return null;
|
|
4030
|
+
}
|
|
4031
|
+
return field.values.join(" ").trim() || null;
|
|
4032
|
+
}
|
|
4033
|
+
function parseOptionalUnitField(field, key) {
|
|
4034
|
+
return field ? parseAtlasUnitValue(singleFieldValue2(field), field.location, key) : void 0;
|
|
4035
|
+
}
|
|
4036
|
+
function parseOptionalNumberField(field, key) {
|
|
4037
|
+
return field ? parseAtlasNumber(singleFieldValue2(field), key, field.location) : void 0;
|
|
4038
|
+
}
|
|
4039
|
+
function singleFieldValue2(field) {
|
|
4040
|
+
return singleAtlasValue(field.values, field.key, field.location);
|
|
4041
|
+
}
|
|
4042
|
+
function getDraftObjectFieldSpec(key) {
|
|
4043
|
+
return DRAFT_OBJECT_FIELD_SPECS.get(key);
|
|
4044
|
+
}
|
|
4045
|
+
function validateDraftObjectFieldCompatibility(fields, objectType) {
|
|
4046
|
+
for (const field of fields) {
|
|
4047
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
4048
|
+
if (!spec) {
|
|
4049
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4050
|
+
}
|
|
4051
|
+
if (spec.legacySchema) {
|
|
4052
|
+
ensureAtlasFieldSupported(field.key, objectType, field.location);
|
|
4053
|
+
continue;
|
|
4054
|
+
}
|
|
4055
|
+
if ((field.key === "renderLabel" || field.key === "renderOrbit" || field.key === "tidalLock") && field.values.length !== 1) {
|
|
4056
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4061
|
+
if (sourceSchemaVersion === "2.1") {
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
diagnostics.push({
|
|
4065
|
+
code: "parse.schema21.featureCompatibility",
|
|
4066
|
+
severity: "warning",
|
|
4067
|
+
source: "parse",
|
|
4068
|
+
message: `Feature "${featureName}" requires schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
4069
|
+
line: location.line,
|
|
4070
|
+
column: location.column
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
function preprocessAtlasSource(source) {
|
|
4074
|
+
const chars = [...source];
|
|
4075
|
+
const comments = [];
|
|
4076
|
+
let inString = false;
|
|
4077
|
+
let inBlockComment = false;
|
|
4078
|
+
let blockCommentStart = null;
|
|
4079
|
+
let line = 1;
|
|
4080
|
+
let column = 1;
|
|
4081
|
+
for (let index = 0; index < chars.length; index++) {
|
|
4082
|
+
const ch = chars[index];
|
|
4083
|
+
const next = chars[index + 1];
|
|
4084
|
+
if (inBlockComment) {
|
|
4085
|
+
if (ch === "*" && next === "/") {
|
|
4086
|
+
chars[index] = " ";
|
|
4087
|
+
chars[index + 1] = " ";
|
|
4088
|
+
inBlockComment = false;
|
|
4089
|
+
blockCommentStart = null;
|
|
4090
|
+
index++;
|
|
4091
|
+
column += 2;
|
|
4092
|
+
continue;
|
|
4093
|
+
}
|
|
4094
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
4095
|
+
chars[index] = " ";
|
|
4096
|
+
}
|
|
4097
|
+
if (ch === "\n") {
|
|
4098
|
+
line++;
|
|
4099
|
+
column = 1;
|
|
4100
|
+
} else {
|
|
4101
|
+
column++;
|
|
4102
|
+
}
|
|
4103
|
+
continue;
|
|
4104
|
+
}
|
|
4105
|
+
if (!inString && ch === "/" && next === "*") {
|
|
4106
|
+
comments.push({ kind: "block", line, column });
|
|
4107
|
+
chars[index] = " ";
|
|
4108
|
+
chars[index + 1] = " ";
|
|
4109
|
+
inBlockComment = true;
|
|
4110
|
+
blockCommentStart = { line, column };
|
|
4111
|
+
index++;
|
|
4112
|
+
column += 2;
|
|
4113
|
+
continue;
|
|
4114
|
+
}
|
|
4115
|
+
if (!inString && ch === "#" && !isHexColorLiteral(chars, index)) {
|
|
4116
|
+
comments.push({ kind: "line", line, column });
|
|
4117
|
+
chars[index] = " ";
|
|
4118
|
+
let inner = index + 1;
|
|
4119
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
4120
|
+
chars[inner] = " ";
|
|
4121
|
+
inner++;
|
|
4122
|
+
}
|
|
4123
|
+
column += inner - index;
|
|
4124
|
+
index = inner - 1;
|
|
4125
|
+
continue;
|
|
4126
|
+
}
|
|
4127
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
4128
|
+
inString = !inString;
|
|
4129
|
+
}
|
|
4130
|
+
if (ch === "\n") {
|
|
4131
|
+
line++;
|
|
4132
|
+
column = 1;
|
|
4133
|
+
} else {
|
|
4134
|
+
column++;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
if (inBlockComment) {
|
|
4138
|
+
throw WorldOrbitError.fromLocation("Unclosed block comment", blockCommentStart ?? void 0);
|
|
4139
|
+
}
|
|
4140
|
+
return {
|
|
4141
|
+
source: chars.join(""),
|
|
4142
|
+
comments
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
function isHexColorLiteral(chars, start) {
|
|
4146
|
+
let index = start + 1;
|
|
4147
|
+
let length = 0;
|
|
4148
|
+
while (index < chars.length && /[0-9a-f]/i.test(chars[index] ?? "")) {
|
|
4149
|
+
index++;
|
|
4150
|
+
length++;
|
|
4151
|
+
}
|
|
4152
|
+
if (![3, 4, 6, 8].includes(length)) {
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
const next = chars[index];
|
|
4156
|
+
return next === void 0 || next === " " || next === " " || next === "\r" || next === "\n";
|
|
3003
4157
|
}
|
|
3004
4158
|
|
|
3005
4159
|
// packages/core/dist/load.js
|
|
3006
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0)?$/i;
|
|
4160
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
4161
|
+
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
3007
4162
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
3008
4163
|
function detectWorldOrbitSchemaVersion(source) {
|
|
3009
|
-
for (const line of source.split(/\r?\n/)) {
|
|
4164
|
+
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
3010
4165
|
const trimmed = line.trim();
|
|
3011
4166
|
if (!trimmed) {
|
|
3012
4167
|
continue;
|
|
@@ -3014,6 +4169,9 @@
|
|
|
3014
4169
|
if (LEGACY_DRAFT_SCHEMA_PATTERN.test(trimmed)) {
|
|
3015
4170
|
return "2.0-draft";
|
|
3016
4171
|
}
|
|
4172
|
+
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
4173
|
+
return "2.1";
|
|
4174
|
+
}
|
|
3017
4175
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
3018
4176
|
return "2.0";
|
|
3019
4177
|
}
|
|
@@ -3021,6 +4179,49 @@
|
|
|
3021
4179
|
}
|
|
3022
4180
|
return "1.0";
|
|
3023
4181
|
}
|
|
4182
|
+
function stripCommentsForSchemaDetection(source) {
|
|
4183
|
+
const chars = [...source];
|
|
4184
|
+
let inString = false;
|
|
4185
|
+
let inBlockComment = false;
|
|
4186
|
+
for (let index = 0; index < chars.length; index++) {
|
|
4187
|
+
const ch = chars[index];
|
|
4188
|
+
const next = chars[index + 1];
|
|
4189
|
+
if (inBlockComment) {
|
|
4190
|
+
if (ch === "*" && next === "/") {
|
|
4191
|
+
chars[index] = " ";
|
|
4192
|
+
chars[index + 1] = " ";
|
|
4193
|
+
inBlockComment = false;
|
|
4194
|
+
index++;
|
|
4195
|
+
continue;
|
|
4196
|
+
}
|
|
4197
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
4198
|
+
chars[index] = " ";
|
|
4199
|
+
}
|
|
4200
|
+
continue;
|
|
4201
|
+
}
|
|
4202
|
+
if (!inString && ch === "/" && next === "*") {
|
|
4203
|
+
chars[index] = " ";
|
|
4204
|
+
chars[index + 1] = " ";
|
|
4205
|
+
inBlockComment = true;
|
|
4206
|
+
index++;
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
4209
|
+
if (!inString && ch === "#") {
|
|
4210
|
+
chars[index] = " ";
|
|
4211
|
+
let inner = index + 1;
|
|
4212
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
4213
|
+
chars[inner] = " ";
|
|
4214
|
+
inner++;
|
|
4215
|
+
}
|
|
4216
|
+
index = inner - 1;
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
4220
|
+
inString = !inString;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
return chars.join("");
|
|
4224
|
+
}
|
|
3024
4225
|
function loadWorldOrbitSource(source) {
|
|
3025
4226
|
const result = loadWorldOrbitSourceWithDiagnostics(source);
|
|
3026
4227
|
if (!result.ok || !result.value) {
|
|
@@ -3031,36 +4232,36 @@
|
|
|
3031
4232
|
}
|
|
3032
4233
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
3033
4234
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
3034
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft") {
|
|
4235
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
3035
4236
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
3036
4237
|
}
|
|
3037
4238
|
let ast;
|
|
3038
4239
|
try {
|
|
3039
4240
|
ast = parseWorldOrbit(source);
|
|
3040
|
-
} catch (
|
|
4241
|
+
} catch (error2) {
|
|
3041
4242
|
return {
|
|
3042
4243
|
ok: false,
|
|
3043
4244
|
value: null,
|
|
3044
|
-
diagnostics: [diagnosticFromError(
|
|
4245
|
+
diagnostics: [diagnosticFromError(error2, "parse")]
|
|
3045
4246
|
};
|
|
3046
4247
|
}
|
|
3047
4248
|
let document2;
|
|
3048
4249
|
try {
|
|
3049
4250
|
document2 = normalizeDocument(ast);
|
|
3050
|
-
} catch (
|
|
4251
|
+
} catch (error2) {
|
|
3051
4252
|
return {
|
|
3052
4253
|
ok: false,
|
|
3053
4254
|
value: null,
|
|
3054
|
-
diagnostics: [diagnosticFromError(
|
|
4255
|
+
diagnostics: [diagnosticFromError(error2, "normalize")]
|
|
3055
4256
|
};
|
|
3056
4257
|
}
|
|
3057
4258
|
try {
|
|
3058
4259
|
validateDocument(document2);
|
|
3059
|
-
} catch (
|
|
4260
|
+
} catch (error2) {
|
|
3060
4261
|
return {
|
|
3061
4262
|
ok: false,
|
|
3062
4263
|
value: null,
|
|
3063
|
-
diagnostics: [diagnosticFromError(
|
|
4264
|
+
diagnostics: [diagnosticFromError(error2, "validate")]
|
|
3064
4265
|
};
|
|
3065
4266
|
}
|
|
3066
4267
|
return {
|
|
@@ -3080,30 +4281,29 @@
|
|
|
3080
4281
|
let atlasDocument;
|
|
3081
4282
|
try {
|
|
3082
4283
|
atlasDocument = parseWorldOrbitAtlas(source);
|
|
3083
|
-
} catch (
|
|
4284
|
+
} catch (error2) {
|
|
3084
4285
|
return {
|
|
3085
4286
|
ok: false,
|
|
3086
4287
|
value: null,
|
|
3087
|
-
diagnostics: [diagnosticFromError(
|
|
4288
|
+
diagnostics: [diagnosticFromError(error2, "parse", "load.atlas.failed")]
|
|
3088
4289
|
};
|
|
3089
4290
|
}
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
document2 = materializeAtlasDocument(atlasDocument);
|
|
3093
|
-
} catch (error) {
|
|
4291
|
+
const atlasDiagnostics = [...atlasDocument.diagnostics];
|
|
4292
|
+
if (atlasDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
3094
4293
|
return {
|
|
3095
4294
|
ok: false,
|
|
3096
4295
|
value: null,
|
|
3097
|
-
diagnostics:
|
|
4296
|
+
diagnostics: atlasDiagnostics
|
|
3098
4297
|
};
|
|
3099
4298
|
}
|
|
4299
|
+
let document2;
|
|
3100
4300
|
try {
|
|
3101
|
-
|
|
3102
|
-
} catch (
|
|
4301
|
+
document2 = materializeAtlasDocument(atlasDocument);
|
|
4302
|
+
} catch (error2) {
|
|
3103
4303
|
return {
|
|
3104
4304
|
ok: false,
|
|
3105
4305
|
value: null,
|
|
3106
|
-
diagnostics: [diagnosticFromError(
|
|
4306
|
+
diagnostics: [diagnosticFromError(error2, "normalize", "load.atlas.materialize.failed")]
|
|
3107
4307
|
};
|
|
3108
4308
|
}
|
|
3109
4309
|
const loaded = {
|
|
@@ -3112,12 +4312,12 @@
|
|
|
3112
4312
|
document: document2,
|
|
3113
4313
|
atlasDocument,
|
|
3114
4314
|
draftDocument: atlasDocument,
|
|
3115
|
-
diagnostics:
|
|
4315
|
+
diagnostics: atlasDiagnostics
|
|
3116
4316
|
};
|
|
3117
4317
|
return {
|
|
3118
4318
|
ok: true,
|
|
3119
4319
|
value: loaded,
|
|
3120
|
-
diagnostics:
|
|
4320
|
+
diagnostics: atlasDiagnostics
|
|
3121
4321
|
};
|
|
3122
4322
|
}
|
|
3123
4323
|
|
|
@@ -3125,6 +4325,7 @@
|
|
|
3125
4325
|
var DEFAULT_LAYERS = {
|
|
3126
4326
|
background: true,
|
|
3127
4327
|
guides: true,
|
|
4328
|
+
relations: true,
|
|
3128
4329
|
orbits: true,
|
|
3129
4330
|
objects: true,
|
|
3130
4331
|
labels: true,
|
|
@@ -3139,6 +4340,7 @@
|
|
|
3139
4340
|
backgroundGlow: "rgba(240, 180, 100, 0.18)",
|
|
3140
4341
|
panel: "rgba(7, 17, 27, 0.9)",
|
|
3141
4342
|
panelLine: "rgba(168, 207, 242, 0.18)",
|
|
4343
|
+
relation: "rgba(240, 180, 100, 0.42)",
|
|
3142
4344
|
orbit: "rgba(163, 209, 255, 0.24)",
|
|
3143
4345
|
orbitBand: "rgba(255, 190, 120, 0.28)",
|
|
3144
4346
|
guide: "rgba(255, 255, 255, 0.04)",
|
|
@@ -3161,6 +4363,7 @@
|
|
|
3161
4363
|
backgroundGlow: "rgba(120, 255, 215, 0.16)",
|
|
3162
4364
|
panel: "rgba(7, 20, 30, 0.9)",
|
|
3163
4365
|
panelLine: "rgba(120, 255, 215, 0.16)",
|
|
4366
|
+
relation: "rgba(156, 231, 255, 0.42)",
|
|
3164
4367
|
orbit: "rgba(120, 255, 215, 0.2)",
|
|
3165
4368
|
orbitBand: "rgba(137, 185, 255, 0.24)",
|
|
3166
4369
|
guide: "rgba(255, 255, 255, 0.035)",
|
|
@@ -3183,6 +4386,7 @@
|
|
|
3183
4386
|
backgroundGlow: "rgba(255, 127, 95, 0.18)",
|
|
3184
4387
|
panel: "rgba(24, 9, 13, 0.9)",
|
|
3185
4388
|
panelLine: "rgba(255, 166, 149, 0.16)",
|
|
4389
|
+
relation: "rgba(255, 178, 125, 0.42)",
|
|
3186
4390
|
orbit: "rgba(255, 188, 164, 0.22)",
|
|
3187
4391
|
orbitBand: "rgba(255, 214, 139, 0.24)",
|
|
3188
4392
|
guide: "rgba(255, 255, 255, 0.03)",
|
|
@@ -3264,7 +4468,11 @@
|
|
|
3264
4468
|
return false;
|
|
3265
4469
|
}
|
|
3266
4470
|
if (filter.groupIds?.length && (!object.groupId || !filter.groupIds.includes(object.groupId))) {
|
|
3267
|
-
|
|
4471
|
+
const hasSemanticMatch = object.semanticGroupIds.length > 0 && filter.groupIds.some((groupId) => object.semanticGroupIds.includes(groupId));
|
|
4472
|
+
const hasLegacyMatch = Boolean(object.groupId && filter.groupIds.includes(object.groupId));
|
|
4473
|
+
if (!hasSemanticMatch && !hasLegacyMatch) {
|
|
4474
|
+
return false;
|
|
4475
|
+
}
|
|
3268
4476
|
}
|
|
3269
4477
|
if (filter.tags?.length) {
|
|
3270
4478
|
const objectTags = Array.isArray(object.object.properties.tags) ? object.object.properties.tags.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3320,6 +4528,7 @@
|
|
|
3320
4528
|
const imageDefinitions = buildImageDefinitions(visibleObjects);
|
|
3321
4529
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
3322
4530
|
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("") : "";
|
|
4531
|
+
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("") : "";
|
|
3323
4532
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
3324
4533
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
3325
4534
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -3354,6 +4563,7 @@
|
|
|
3354
4563
|
.wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
|
|
3355
4564
|
.wo-orbit-front { opacity: 0.9; }
|
|
3356
4565
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
4566
|
+
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
3357
4567
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
3358
4568
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
3359
4569
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -3387,6 +4597,7 @@
|
|
|
3387
4597
|
<g data-worldorbit-world-content="true">
|
|
3388
4598
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
3389
4599
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
4600
|
+
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
3390
4601
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
3391
4602
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
3392
4603
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -3430,10 +4641,11 @@
|
|
|
3430
4641
|
function renderSceneObject(sceneObject, selectedObjectId, theme) {
|
|
3431
4642
|
const { object, x, y, radius, visualRadius } = sceneObject;
|
|
3432
4643
|
const selectionClass = selectedObjectId === sceneObject.objectId ? " wo-object-selected" : "";
|
|
4644
|
+
const kindClass = object.properties.kind ? ` wo-kind-${String(object.properties.kind).toLowerCase().replace(/[^a-z0-9-]/g, "-")}` : "";
|
|
3433
4645
|
const palette = resolveObjectPalette(sceneObject, theme);
|
|
3434
4646
|
const imageMarkup = renderObjectImage(sceneObject);
|
|
3435
4647
|
const outlineMarkup = imageMarkup ? renderObjectBody(object, x, y, radius, palette, { outlineOnly: true }) : "";
|
|
3436
|
-
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}`)}">
|
|
4648
|
+
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}`)}">
|
|
3437
4649
|
<circle class="wo-selection-ring" cx="${x}" cy="${y}" r="${visualRadius + 8}" />
|
|
3438
4650
|
${renderAtmosphere(sceneObject, palette)}
|
|
3439
4651
|
${renderObjectBody(object, x, y, radius, palette)}
|
|
@@ -3467,8 +4679,33 @@
|
|
|
3467
4679
|
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3468
4680
|
case "structure":
|
|
3469
4681
|
return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3470
|
-
case "phenomenon":
|
|
4682
|
+
case "phenomenon": {
|
|
4683
|
+
const kind = String(object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
4684
|
+
if (options.outlineOnly) {
|
|
4685
|
+
if (kind === "black-hole" || kind === "nebula" || kind === "galaxy" || kind === "dwarf-galaxy") {
|
|
4686
|
+
return `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
4687
|
+
}
|
|
4688
|
+
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
4689
|
+
}
|
|
4690
|
+
if (kind === "black-hole") {
|
|
4691
|
+
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" />
|
|
4692
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="2" />`;
|
|
4693
|
+
}
|
|
4694
|
+
if (kind === "galaxy") {
|
|
4695
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.6}" ry="${radius}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
4696
|
+
<ellipse cx="${x}" cy="${y}" rx="${radius * 1.5}" ry="${radius * 0.42}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.2" />
|
|
4697
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.28}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
4698
|
+
}
|
|
4699
|
+
if (kind === "dwarf-galaxy") {
|
|
4700
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 1.6}" ry="${radius * 0.55}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />
|
|
4701
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.25}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
4702
|
+
}
|
|
4703
|
+
if (kind === "nebula") {
|
|
4704
|
+
return `<circle cx="${x}" cy="${y}" r="${radius * 2.2}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
4705
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />`;
|
|
4706
|
+
}
|
|
3471
4707
|
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
4708
|
+
}
|
|
3472
4709
|
}
|
|
3473
4710
|
}
|
|
3474
4711
|
function renderAtmosphere(sceneObject, palette) {
|
|
@@ -3537,7 +4774,8 @@
|
|
|
3537
4774
|
}
|
|
3538
4775
|
}
|
|
3539
4776
|
function resolveObjectPalette(sceneObject, theme) {
|
|
3540
|
-
const
|
|
4777
|
+
const kind = String(sceneObject.object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
4778
|
+
const base = basePaletteForType(sceneObject.object.type, kind, theme);
|
|
3541
4779
|
const customFill = sceneObject.fillColor && isColorLike(sceneObject.fillColor) ? sceneObject.fillColor : base.fill;
|
|
3542
4780
|
const albedo = numericValue(sceneObject.object.properties.albedo);
|
|
3543
4781
|
const temperature = numericValue(sceneObject.object.properties.temperature);
|
|
@@ -3553,7 +4791,7 @@
|
|
|
3553
4791
|
tail: sceneObject.object.type === "comet" ? rgbaString(mixColors(fill, "#ffffff", 0.5) ?? fill, 0.72) : void 0
|
|
3554
4792
|
};
|
|
3555
4793
|
}
|
|
3556
|
-
function basePaletteForType(type, theme) {
|
|
4794
|
+
function basePaletteForType(type, kind, theme) {
|
|
3557
4795
|
switch (type) {
|
|
3558
4796
|
case "star":
|
|
3559
4797
|
return {
|
|
@@ -3575,8 +4813,26 @@
|
|
|
3575
4813
|
case "structure":
|
|
3576
4814
|
return { fill: theme.accentStrong, stroke: "#fff2ea" };
|
|
3577
4815
|
case "phenomenon":
|
|
3578
|
-
return
|
|
4816
|
+
return kindPhenomenonPalette(kind);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
function kindPhenomenonPalette(kind) {
|
|
4820
|
+
if (kind === "galaxy") {
|
|
4821
|
+
return { fill: "rgba(165,125,255,0.55)", stroke: "rgba(210,185,255,0.75)", halo: "rgba(160,120,255,0.10)", core: "#ede0ff" };
|
|
4822
|
+
}
|
|
4823
|
+
if (kind === "dwarf-galaxy") {
|
|
4824
|
+
return { fill: "rgba(190,165,255,0.45)", stroke: "rgba(220,205,255,0.75)", core: "#ddd0ff" };
|
|
4825
|
+
}
|
|
4826
|
+
if (kind === "black-hole") {
|
|
4827
|
+
return { fill: "#040408", stroke: "#ff6a00", accentRing: "rgba(255,140,20,0.72)" };
|
|
4828
|
+
}
|
|
4829
|
+
if (kind === "nebula") {
|
|
4830
|
+
return { fill: "rgba(105,205,255,0.45)", stroke: "rgba(180,235,255,0.72)", halo: "rgba(100,200,255,0.08)" };
|
|
4831
|
+
}
|
|
4832
|
+
if (kind === "void") {
|
|
4833
|
+
return { fill: "#05080f", stroke: "rgba(130,160,255,0.4)" };
|
|
3579
4834
|
}
|
|
4835
|
+
return { fill: "#78ffd7", stroke: "#e9fff7" };
|
|
3580
4836
|
}
|
|
3581
4837
|
function applyTemperatureAndAlbedo(baseColor, temperature, albedo, type) {
|
|
3582
4838
|
let nextColor = baseColor;
|
|
@@ -3843,11 +5099,11 @@
|
|
|
3843
5099
|
});
|
|
3844
5100
|
}
|
|
3845
5101
|
return `<figure class="${escapeAttribute3(options.className ?? "worldorbit-block worldorbit-static")}">${renderSceneToSvg(scene, options)}</figure>`;
|
|
3846
|
-
} catch (
|
|
5102
|
+
} catch (error2) {
|
|
3847
5103
|
if (options.strict) {
|
|
3848
|
-
throw
|
|
5104
|
+
throw error2;
|
|
3849
5105
|
}
|
|
3850
|
-
return renderWorldOrbitError(
|
|
5106
|
+
return renderWorldOrbitError(error2 instanceof Error ? error2.message : String(error2));
|
|
3851
5107
|
}
|
|
3852
5108
|
}
|
|
3853
5109
|
function renderWorldOrbitError(message) {
|