worldorbit 2.6.0 → 3.0.1
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 +20 -9
- package/dist/browser/core/dist/atlas-edit.d.ts +11 -0
- package/dist/browser/core/dist/atlas-edit.js +347 -0
- package/dist/browser/core/dist/atlas-utils.d.ts +22 -0
- package/dist/browser/core/dist/atlas-utils.js +189 -0
- package/dist/browser/core/dist/atlas-validate.d.ts +2 -0
- package/dist/browser/core/dist/atlas-validate.js +488 -0
- package/dist/browser/core/dist/diagnostics.d.ts +10 -0
- package/dist/browser/core/dist/diagnostics.js +109 -0
- package/dist/browser/core/dist/draft-parse.d.ts +3 -0
- package/dist/browser/core/dist/draft-parse.js +1654 -0
- package/dist/browser/core/dist/draft.d.ts +21 -0
- package/dist/browser/core/dist/draft.js +482 -0
- package/dist/browser/core/dist/errors.d.ts +7 -0
- package/dist/browser/core/dist/errors.js +16 -0
- package/dist/browser/core/dist/format.d.ts +4 -0
- package/dist/browser/core/dist/format.js +613 -0
- package/dist/browser/core/dist/index.d.ts +29 -0
- package/dist/browser/core/dist/index.js +35 -6542
- package/dist/browser/core/dist/load.d.ts +4 -0
- package/dist/browser/core/dist/load.js +182 -0
- package/dist/browser/core/dist/markdown.d.ts +2 -0
- package/dist/browser/core/dist/markdown.js +37 -0
- package/dist/browser/core/dist/normalize.d.ts +2 -0
- package/dist/browser/core/dist/normalize.js +312 -0
- package/dist/browser/core/dist/parse.d.ts +2 -0
- package/dist/browser/core/dist/parse.js +133 -0
- package/dist/browser/core/dist/scene.d.ts +3 -0
- package/dist/browser/core/dist/scene.js +1901 -0
- package/dist/browser/core/dist/schema.d.ts +8 -0
- package/dist/browser/core/dist/schema.js +298 -0
- package/dist/browser/core/dist/spatial-scene.d.ts +3 -0
- package/dist/browser/core/dist/spatial-scene.js +420 -0
- package/dist/browser/core/dist/tokenize.d.ts +4 -0
- package/dist/browser/core/dist/tokenize.js +68 -0
- package/dist/browser/core/dist/types.d.ts +637 -0
- package/dist/browser/core/dist/types.js +1 -0
- package/dist/browser/core/dist/validate.d.ts +2 -0
- package/dist/browser/core/dist/validate.js +56 -0
- package/dist/browser/editor/dist/editor.d.ts +2 -0
- package/dist/browser/editor/dist/editor.js +3700 -0
- package/dist/browser/editor/dist/index.d.ts +2 -0
- package/dist/browser/editor/dist/index.js +1 -12250
- package/dist/browser/editor/dist/types.d.ts +59 -0
- package/dist/browser/editor/dist/types.js +1 -0
- package/dist/browser/markdown/dist/html.d.ts +3 -0
- package/dist/browser/markdown/dist/html.js +64 -0
- package/dist/browser/markdown/dist/index.d.ts +4 -0
- package/dist/browser/markdown/dist/index.js +3 -6179
- package/dist/browser/markdown/dist/rehype.d.ts +10 -0
- package/dist/browser/markdown/dist/rehype.js +49 -0
- package/dist/browser/markdown/dist/remark.d.ts +9 -0
- package/dist/browser/markdown/dist/remark.js +28 -0
- package/dist/browser/markdown/dist/types.d.ts +11 -0
- package/dist/browser/markdown/dist/types.js +1 -0
- package/dist/browser/viewer/dist/atlas-state.d.ts +12 -0
- package/dist/browser/viewer/dist/atlas-state.js +269 -0
- package/dist/browser/viewer/dist/atlas-viewer.d.ts +2 -0
- package/dist/browser/viewer/dist/atlas-viewer.js +495 -0
- package/dist/browser/viewer/dist/custom-element.d.ts +1 -0
- package/dist/browser/viewer/dist/custom-element.js +78 -0
- package/dist/browser/viewer/dist/embed.d.ts +24 -0
- package/dist/browser/viewer/dist/embed.js +172 -0
- package/dist/browser/viewer/dist/errors.d.ts +6 -0
- package/dist/browser/viewer/dist/errors.js +12 -0
- package/dist/browser/viewer/dist/index.d.ts +10 -0
- package/dist/browser/viewer/dist/index.js +9 -8334
- package/dist/browser/viewer/dist/minimap.d.ts +3 -0
- package/dist/browser/viewer/dist/minimap.js +63 -0
- package/dist/browser/viewer/dist/render.d.ts +6 -0
- package/dist/browser/viewer/dist/render.js +670 -0
- package/dist/browser/viewer/dist/runtime-3d.d.ts +19 -0
- package/dist/browser/viewer/dist/runtime-3d.js +494 -0
- package/dist/browser/viewer/dist/theme.d.ts +4 -0
- package/dist/browser/viewer/dist/theme.js +103 -0
- package/dist/browser/viewer/dist/tooltip.d.ts +3 -0
- package/dist/browser/viewer/dist/tooltip.js +198 -0
- package/dist/browser/viewer/dist/types.d.ts +292 -0
- package/dist/browser/viewer/dist/types.js +1 -0
- package/dist/browser/viewer/dist/vendor/three.module.js +53032 -0
- package/dist/browser/viewer/dist/viewer-state.d.ts +19 -0
- package/dist/browser/viewer/dist/viewer-state.js +162 -0
- package/dist/browser/viewer/dist/viewer.d.ts +2 -0
- package/dist/browser/viewer/dist/viewer.js +1662 -0
- package/dist/unpkg/core/dist/atlas-edit.d.ts +11 -0
- package/dist/unpkg/core/dist/atlas-edit.js +347 -0
- package/dist/unpkg/core/dist/atlas-utils.d.ts +22 -0
- package/dist/unpkg/core/dist/atlas-utils.js +189 -0
- package/dist/unpkg/core/dist/atlas-validate.d.ts +2 -0
- package/dist/unpkg/core/dist/atlas-validate.js +488 -0
- package/dist/unpkg/core/dist/diagnostics.d.ts +10 -0
- package/dist/unpkg/core/dist/diagnostics.js +109 -0
- package/dist/unpkg/core/dist/draft-parse.d.ts +3 -0
- package/dist/unpkg/core/dist/draft-parse.js +1654 -0
- package/dist/unpkg/core/dist/draft.d.ts +21 -0
- package/dist/unpkg/core/dist/draft.js +482 -0
- package/dist/unpkg/core/dist/errors.d.ts +7 -0
- package/dist/unpkg/core/dist/errors.js +16 -0
- package/dist/unpkg/core/dist/format.d.ts +4 -0
- package/dist/unpkg/core/dist/format.js +613 -0
- package/dist/unpkg/core/dist/index.d.ts +29 -0
- package/dist/unpkg/core/dist/index.js +35 -6614
- package/dist/unpkg/core/dist/load.d.ts +4 -0
- package/dist/unpkg/core/dist/load.js +182 -0
- package/dist/unpkg/core/dist/markdown.d.ts +2 -0
- package/dist/unpkg/core/dist/markdown.js +37 -0
- package/dist/unpkg/core/dist/normalize.d.ts +2 -0
- package/dist/unpkg/core/dist/normalize.js +312 -0
- package/dist/unpkg/core/dist/parse.d.ts +2 -0
- package/dist/unpkg/core/dist/parse.js +133 -0
- package/dist/unpkg/core/dist/scene.d.ts +3 -0
- package/dist/unpkg/core/dist/scene.js +1901 -0
- package/dist/unpkg/core/dist/schema.d.ts +8 -0
- package/dist/unpkg/core/dist/schema.js +298 -0
- package/dist/unpkg/core/dist/spatial-scene.d.ts +3 -0
- package/dist/unpkg/core/dist/spatial-scene.js +420 -0
- package/dist/unpkg/core/dist/tokenize.d.ts +4 -0
- package/dist/unpkg/core/dist/tokenize.js +68 -0
- package/dist/unpkg/core/dist/types.d.ts +637 -0
- package/dist/unpkg/core/dist/types.js +1 -0
- package/dist/unpkg/core/dist/validate.d.ts +2 -0
- package/dist/unpkg/core/dist/validate.js +56 -0
- package/dist/unpkg/editor/dist/editor.d.ts +2 -0
- package/dist/unpkg/editor/dist/editor.js +3700 -0
- package/dist/unpkg/editor/dist/index.d.ts +2 -0
- package/dist/unpkg/editor/dist/index.js +1 -12275
- package/dist/unpkg/editor/dist/types.d.ts +59 -0
- package/dist/unpkg/editor/dist/types.js +1 -0
- package/dist/unpkg/markdown/dist/html.d.ts +3 -0
- package/dist/unpkg/markdown/dist/html.js +64 -0
- package/dist/unpkg/markdown/dist/index.d.ts +4 -0
- package/dist/unpkg/markdown/dist/index.js +3 -6207
- package/dist/unpkg/markdown/dist/rehype.d.ts +10 -0
- package/dist/unpkg/markdown/dist/rehype.js +49 -0
- package/dist/unpkg/markdown/dist/remark.d.ts +9 -0
- package/dist/unpkg/markdown/dist/remark.js +28 -0
- package/dist/unpkg/markdown/dist/types.d.ts +11 -0
- package/dist/unpkg/markdown/dist/types.js +1 -0
- package/dist/unpkg/viewer/dist/atlas-state.d.ts +12 -0
- package/dist/unpkg/viewer/dist/atlas-state.js +269 -0
- package/dist/unpkg/viewer/dist/atlas-viewer.d.ts +2 -0
- package/dist/unpkg/viewer/dist/atlas-viewer.js +495 -0
- package/dist/unpkg/viewer/dist/custom-element.d.ts +1 -0
- package/dist/unpkg/viewer/dist/custom-element.js +78 -0
- package/dist/unpkg/viewer/dist/embed.d.ts +24 -0
- package/dist/unpkg/viewer/dist/embed.js +172 -0
- package/dist/unpkg/viewer/dist/errors.d.ts +6 -0
- package/dist/unpkg/viewer/dist/errors.js +12 -0
- package/dist/unpkg/viewer/dist/index.d.ts +10 -0
- package/dist/unpkg/viewer/dist/index.js +9 -8391
- package/dist/unpkg/viewer/dist/minimap.d.ts +3 -0
- package/dist/unpkg/viewer/dist/minimap.js +63 -0
- package/dist/unpkg/viewer/dist/render.d.ts +6 -0
- package/dist/unpkg/viewer/dist/render.js +670 -0
- package/dist/unpkg/viewer/dist/runtime-3d.d.ts +19 -0
- package/dist/unpkg/viewer/dist/runtime-3d.js +494 -0
- package/dist/unpkg/viewer/dist/theme.d.ts +4 -0
- package/dist/unpkg/viewer/dist/theme.js +103 -0
- package/dist/unpkg/viewer/dist/tooltip.d.ts +3 -0
- package/dist/unpkg/viewer/dist/tooltip.js +198 -0
- package/dist/unpkg/viewer/dist/types.d.ts +292 -0
- package/dist/unpkg/viewer/dist/types.js +1 -0
- package/dist/unpkg/viewer/dist/vendor/three.module.js +53032 -0
- package/dist/unpkg/viewer/dist/viewer-state.d.ts +19 -0
- package/dist/unpkg/viewer/dist/viewer-state.js +162 -0
- package/dist/unpkg/viewer/dist/viewer.d.ts +2 -0
- package/dist/unpkg/viewer/dist/viewer.js +1662 -0
- package/dist/unpkg/worldorbit-core.min.js +10 -10
- package/dist/unpkg/worldorbit-editor.min.js +4109 -256
- package/dist/unpkg/worldorbit-markdown.min.js +26 -26
- package/dist/unpkg/worldorbit-viewer.min.js +3945 -92
- package/dist/unpkg/worldorbit.js +32219 -199
- package/dist/unpkg/worldorbit.min.js +3949 -96
- package/package.json +1 -1
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/spatial-scene.d.ts +3 -0
- package/packages/core/dist/spatial-scene.js +420 -0
- package/packages/core/dist/types.d.ts +105 -0
- package/packages/editor/dist/editor.js +25 -4
- package/packages/editor/dist/types.d.ts +4 -0
- package/packages/markdown/dist/html.js +10 -3
- package/packages/viewer/dist/atlas-state.js +3 -0
- package/packages/viewer/dist/atlas-viewer.js +1 -0
- package/packages/viewer/dist/custom-element.js +18 -4
- package/packages/viewer/dist/embed.d.ts +5 -1
- package/packages/viewer/dist/embed.js +58 -24
- package/packages/viewer/dist/errors.d.ts +6 -0
- package/packages/viewer/dist/errors.js +12 -0
- package/packages/viewer/dist/index.d.ts +1 -0
- package/packages/viewer/dist/index.js +1 -0
- package/packages/viewer/dist/runtime-3d.d.ts +19 -0
- package/packages/viewer/dist/runtime-3d.js +494 -0
- package/packages/viewer/dist/types.d.ts +21 -2
- package/packages/viewer/dist/vendor/three.module.js +53032 -0
- package/packages/viewer/dist/viewer.js +501 -41
|
@@ -0,0 +1,1654 @@
|
|
|
1
|
+
import { WorldOrbitError } from "./errors.js";
|
|
2
|
+
import { getFieldSchema, WORLDORBIT_OBJECT_TYPES } from "./schema.js";
|
|
3
|
+
import { getIndent, tokenizeLineDetailed } from "./tokenize.js";
|
|
4
|
+
import { ensureAtlasFieldSupported, humanizeIdentifier, normalizeIdentifier, normalizeLegacyScalarValue, parseAtlasAtReference, parseAtlasBoolean, parseAtlasNumber, parseAtlasUnitValue, singleAtlasValue, tryParseAtlasUnitValue, } from "./atlas-utils.js";
|
|
5
|
+
import { collectAtlasDiagnostics } from "./atlas-validate.js";
|
|
6
|
+
const STRUCTURED_TYPED_BLOCKS = new Set([
|
|
7
|
+
"climate",
|
|
8
|
+
"habitability",
|
|
9
|
+
"settlement",
|
|
10
|
+
]);
|
|
11
|
+
const DRAFT_OBJECT_FIELD_SPECS = new Map();
|
|
12
|
+
for (const key of [
|
|
13
|
+
"orbit",
|
|
14
|
+
"distance",
|
|
15
|
+
"semiMajor",
|
|
16
|
+
"eccentricity",
|
|
17
|
+
"period",
|
|
18
|
+
"angle",
|
|
19
|
+
"inclination",
|
|
20
|
+
"phase",
|
|
21
|
+
"at",
|
|
22
|
+
"surface",
|
|
23
|
+
"free",
|
|
24
|
+
"kind",
|
|
25
|
+
"class",
|
|
26
|
+
"culture",
|
|
27
|
+
"tags",
|
|
28
|
+
"color",
|
|
29
|
+
"image",
|
|
30
|
+
"hidden",
|
|
31
|
+
"radius",
|
|
32
|
+
"mass",
|
|
33
|
+
"density",
|
|
34
|
+
"gravity",
|
|
35
|
+
"temperature",
|
|
36
|
+
"albedo",
|
|
37
|
+
"atmosphere",
|
|
38
|
+
"inner",
|
|
39
|
+
"outer",
|
|
40
|
+
"on",
|
|
41
|
+
"source",
|
|
42
|
+
"cycle",
|
|
43
|
+
]) {
|
|
44
|
+
const schema = getFieldSchema(key);
|
|
45
|
+
if (schema) {
|
|
46
|
+
DRAFT_OBJECT_FIELD_SPECS.set(key, {
|
|
47
|
+
key,
|
|
48
|
+
version: "2.0",
|
|
49
|
+
inlineMode: schema.arity === "multiple" ? "multiple" : "single",
|
|
50
|
+
allowRepeat: false,
|
|
51
|
+
legacySchema: schema,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const spec of [
|
|
56
|
+
{ key: "groups", inlineMode: "multiple", allowRepeat: false },
|
|
57
|
+
{ key: "epoch", inlineMode: "single", allowRepeat: false },
|
|
58
|
+
{ key: "referencePlane", inlineMode: "single", allowRepeat: false },
|
|
59
|
+
{ key: "tidalLock", inlineMode: "single", allowRepeat: false },
|
|
60
|
+
{ key: "renderLabel", inlineMode: "single", allowRepeat: false },
|
|
61
|
+
{ key: "renderOrbit", inlineMode: "single", allowRepeat: false },
|
|
62
|
+
{ key: "renderPriority", inlineMode: "single", allowRepeat: false },
|
|
63
|
+
{ key: "resonance", inlineMode: "pair", allowRepeat: false },
|
|
64
|
+
{ key: "derive", inlineMode: "pair", allowRepeat: true },
|
|
65
|
+
{ key: "validate", inlineMode: "single", allowRepeat: true },
|
|
66
|
+
{ key: "locked", inlineMode: "multiple", allowRepeat: false },
|
|
67
|
+
{ key: "tolerance", inlineMode: "pair", allowRepeat: true },
|
|
68
|
+
]) {
|
|
69
|
+
DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
|
|
70
|
+
key: spec.key,
|
|
71
|
+
version: "2.1",
|
|
72
|
+
inlineMode: spec.inlineMode,
|
|
73
|
+
allowRepeat: spec.allowRepeat,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
77
|
+
const EVENT_POSE_FIELD_KEYS = new Set([
|
|
78
|
+
"orbit",
|
|
79
|
+
"distance",
|
|
80
|
+
"semiMajor",
|
|
81
|
+
"eccentricity",
|
|
82
|
+
"period",
|
|
83
|
+
"angle",
|
|
84
|
+
"inclination",
|
|
85
|
+
"phase",
|
|
86
|
+
"at",
|
|
87
|
+
"surface",
|
|
88
|
+
"free",
|
|
89
|
+
"inner",
|
|
90
|
+
"outer",
|
|
91
|
+
"epoch",
|
|
92
|
+
"referencePlane",
|
|
93
|
+
]);
|
|
94
|
+
export function parseWorldOrbitAtlas(source) {
|
|
95
|
+
return parseAtlasSource(source);
|
|
96
|
+
}
|
|
97
|
+
export function parseWorldOrbitDraft(source) {
|
|
98
|
+
return parseAtlasSource(source, "2.0-draft");
|
|
99
|
+
}
|
|
100
|
+
function parseAtlasSource(source, forcedOutputVersion) {
|
|
101
|
+
const prepared = preprocessAtlasSource(source);
|
|
102
|
+
const lines = prepared.source.split(/\r?\n/);
|
|
103
|
+
const diagnostics = [];
|
|
104
|
+
let sawSchemaHeader = false;
|
|
105
|
+
let sourceSchemaVersion = "2.0";
|
|
106
|
+
let system = null;
|
|
107
|
+
let section = null;
|
|
108
|
+
const objectNodes = [];
|
|
109
|
+
const groups = [];
|
|
110
|
+
const relations = [];
|
|
111
|
+
const events = [];
|
|
112
|
+
const eventPoseNodes = new Map();
|
|
113
|
+
let sawDefaults = false;
|
|
114
|
+
let sawAtlas = false;
|
|
115
|
+
const viewpointIds = new Set();
|
|
116
|
+
const annotationIds = new Set();
|
|
117
|
+
const groupIds = new Set();
|
|
118
|
+
const relationIds = new Set();
|
|
119
|
+
const eventIds = new Set();
|
|
120
|
+
for (let index = 0; index < lines.length; index++) {
|
|
121
|
+
const rawLine = lines[index];
|
|
122
|
+
const lineNumber = index + 1;
|
|
123
|
+
if (!rawLine.trim()) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const indent = getIndent(rawLine);
|
|
127
|
+
const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
|
|
128
|
+
line: lineNumber,
|
|
129
|
+
columnOffset: indent,
|
|
130
|
+
});
|
|
131
|
+
if (tokens.length === 0) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!sawSchemaHeader) {
|
|
135
|
+
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
136
|
+
sawSchemaHeader = true;
|
|
137
|
+
if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
138
|
+
diagnostics.push({
|
|
139
|
+
code: "parse.schema21.commentCompatibility",
|
|
140
|
+
severity: "warning",
|
|
141
|
+
source: "parse",
|
|
142
|
+
message: `Comments require schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
143
|
+
line: prepared.comments[0].line,
|
|
144
|
+
column: prepared.comments[0].column,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (indent === 0) {
|
|
150
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
151
|
+
if (section.kind === "system") {
|
|
152
|
+
system = section.system;
|
|
153
|
+
}
|
|
154
|
+
else if (section.kind === "defaults") {
|
|
155
|
+
sawDefaults = true;
|
|
156
|
+
}
|
|
157
|
+
else if (section.kind === "atlas") {
|
|
158
|
+
sawAtlas = true;
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!section) {
|
|
163
|
+
throw new WorldOrbitError("Indented line without parent atlas section", lineNumber, indent + 1);
|
|
164
|
+
}
|
|
165
|
+
handleSectionLine(section, indent, tokens, lineNumber);
|
|
166
|
+
}
|
|
167
|
+
if (!sawSchemaHeader) {
|
|
168
|
+
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
169
|
+
}
|
|
170
|
+
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
171
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
172
|
+
const outputVersion = forcedOutputVersion ??
|
|
173
|
+
(sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
174
|
+
const baseDocument = {
|
|
175
|
+
format: "worldorbit",
|
|
176
|
+
sourceVersion: "1.0",
|
|
177
|
+
system,
|
|
178
|
+
groups,
|
|
179
|
+
relations,
|
|
180
|
+
events: normalizedEvents,
|
|
181
|
+
objects,
|
|
182
|
+
diagnostics,
|
|
183
|
+
};
|
|
184
|
+
if (outputVersion === "2.0-draft") {
|
|
185
|
+
const document = {
|
|
186
|
+
...baseDocument,
|
|
187
|
+
version: "2.0-draft",
|
|
188
|
+
schemaVersion: "2.0-draft",
|
|
189
|
+
};
|
|
190
|
+
document.diagnostics.push(...collectAtlasDiagnostics(document, sourceSchemaVersion));
|
|
191
|
+
return document;
|
|
192
|
+
}
|
|
193
|
+
const document = {
|
|
194
|
+
...baseDocument,
|
|
195
|
+
version: outputVersion,
|
|
196
|
+
schemaVersion: outputVersion,
|
|
197
|
+
};
|
|
198
|
+
if (sourceSchemaVersion === "2.0-draft") {
|
|
199
|
+
document.diagnostics.push({
|
|
200
|
+
code: "load.schema.deprecatedDraft",
|
|
201
|
+
severity: "warning",
|
|
202
|
+
source: "upgrade",
|
|
203
|
+
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
document.diagnostics.push(...collectAtlasDiagnostics(document, sourceSchemaVersion));
|
|
207
|
+
return document;
|
|
208
|
+
}
|
|
209
|
+
function assertDraftSchemaHeader(tokens, line) {
|
|
210
|
+
if (tokens.length !== 2 ||
|
|
211
|
+
tokens[0].value.toLowerCase() !== "schema" ||
|
|
212
|
+
!["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
|
|
213
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
214
|
+
}
|
|
215
|
+
const version = tokens[1].value.toLowerCase();
|
|
216
|
+
return version === "2.5"
|
|
217
|
+
? "2.5"
|
|
218
|
+
: version === "2.1"
|
|
219
|
+
? "2.1"
|
|
220
|
+
: version === "2.0-draft"
|
|
221
|
+
? "2.0-draft"
|
|
222
|
+
: "2.0";
|
|
223
|
+
}
|
|
224
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
225
|
+
const keyword = tokens[0]?.value.toLowerCase();
|
|
226
|
+
switch (keyword) {
|
|
227
|
+
case "system":
|
|
228
|
+
if (system) {
|
|
229
|
+
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
230
|
+
}
|
|
231
|
+
return startSystemSection(tokens, line, sourceSchemaVersion, diagnostics);
|
|
232
|
+
case "defaults":
|
|
233
|
+
if (!system) {
|
|
234
|
+
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
235
|
+
}
|
|
236
|
+
if (flags.sawDefaults) {
|
|
237
|
+
throw new WorldOrbitError('Atlas section "defaults" may only appear once', line, tokens[0].column);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
kind: "defaults",
|
|
241
|
+
system,
|
|
242
|
+
sourceSchemaVersion,
|
|
243
|
+
diagnostics,
|
|
244
|
+
seenFields: new Set(),
|
|
245
|
+
};
|
|
246
|
+
case "atlas":
|
|
247
|
+
if (!system) {
|
|
248
|
+
throw new WorldOrbitError('Atlas section "atlas" requires a preceding system declaration', line, tokens[0].column);
|
|
249
|
+
}
|
|
250
|
+
if (flags.sawAtlas) {
|
|
251
|
+
throw new WorldOrbitError('Atlas section "atlas" may only appear once', line, tokens[0].column);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
kind: "atlas",
|
|
255
|
+
system,
|
|
256
|
+
inMetadata: false,
|
|
257
|
+
metadataIndent: null,
|
|
258
|
+
};
|
|
259
|
+
case "viewpoint":
|
|
260
|
+
if (!system) {
|
|
261
|
+
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
262
|
+
}
|
|
263
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
264
|
+
case "annotation":
|
|
265
|
+
if (!system) {
|
|
266
|
+
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
267
|
+
}
|
|
268
|
+
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
269
|
+
case "group":
|
|
270
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "group", { line, column: tokens[0].column });
|
|
271
|
+
return startGroupSection(tokens, line, groups, groupIds);
|
|
272
|
+
case "relation":
|
|
273
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
274
|
+
return startRelationSection(tokens, line, relations, relationIds);
|
|
275
|
+
case "event":
|
|
276
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
277
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
278
|
+
case "object":
|
|
279
|
+
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
280
|
+
default:
|
|
281
|
+
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function startSystemSection(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
285
|
+
if (tokens.length !== 2) {
|
|
286
|
+
throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
|
|
287
|
+
}
|
|
288
|
+
const system = {
|
|
289
|
+
type: "system",
|
|
290
|
+
id: tokens[1].value,
|
|
291
|
+
title: null,
|
|
292
|
+
description: null,
|
|
293
|
+
epoch: null,
|
|
294
|
+
referencePlane: null,
|
|
295
|
+
defaults: {
|
|
296
|
+
view: "topdown",
|
|
297
|
+
scale: null,
|
|
298
|
+
units: null,
|
|
299
|
+
preset: null,
|
|
300
|
+
theme: null,
|
|
301
|
+
},
|
|
302
|
+
atlasMetadata: {},
|
|
303
|
+
viewpoints: [],
|
|
304
|
+
annotations: [],
|
|
305
|
+
};
|
|
306
|
+
return {
|
|
307
|
+
kind: "system",
|
|
308
|
+
system,
|
|
309
|
+
sourceSchemaVersion,
|
|
310
|
+
diagnostics,
|
|
311
|
+
seenFields: new Set(),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
315
|
+
if (tokens.length !== 2) {
|
|
316
|
+
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
317
|
+
}
|
|
318
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
319
|
+
if (!id) {
|
|
320
|
+
throw new WorldOrbitError("Viewpoint id must not be empty", line, tokens[1].column);
|
|
321
|
+
}
|
|
322
|
+
if (viewpointIds.has(id)) {
|
|
323
|
+
throw new WorldOrbitError(`Duplicate viewpoint id "${id}"`, line, tokens[1].column);
|
|
324
|
+
}
|
|
325
|
+
const viewpoint = {
|
|
326
|
+
id,
|
|
327
|
+
label: humanizeIdentifier(id),
|
|
328
|
+
summary: "",
|
|
329
|
+
focusObjectId: null,
|
|
330
|
+
selectedObjectId: null,
|
|
331
|
+
events: [],
|
|
332
|
+
projection: system.defaults.view,
|
|
333
|
+
preset: system.defaults.preset,
|
|
334
|
+
zoom: null,
|
|
335
|
+
rotationDeg: 0,
|
|
336
|
+
camera: null,
|
|
337
|
+
layers: {},
|
|
338
|
+
filter: null,
|
|
339
|
+
};
|
|
340
|
+
system.viewpoints.push(viewpoint);
|
|
341
|
+
viewpointIds.add(id);
|
|
342
|
+
return {
|
|
343
|
+
kind: "viewpoint",
|
|
344
|
+
viewpoint,
|
|
345
|
+
sourceSchemaVersion,
|
|
346
|
+
diagnostics,
|
|
347
|
+
seenFields: new Set(),
|
|
348
|
+
inFilter: false,
|
|
349
|
+
filterIndent: null,
|
|
350
|
+
seenFilterFields: new Set(),
|
|
351
|
+
inCamera: false,
|
|
352
|
+
cameraIndent: null,
|
|
353
|
+
seenCameraFields: new Set(),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
357
|
+
if (tokens.length !== 2) {
|
|
358
|
+
throw new WorldOrbitError("Invalid annotation declaration", line, tokens[0]?.column ?? 1);
|
|
359
|
+
}
|
|
360
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
361
|
+
if (!id) {
|
|
362
|
+
throw new WorldOrbitError("Annotation id must not be empty", line, tokens[1].column);
|
|
363
|
+
}
|
|
364
|
+
if (annotationIds.has(id)) {
|
|
365
|
+
throw new WorldOrbitError(`Duplicate annotation id "${id}"`, line, tokens[1].column);
|
|
366
|
+
}
|
|
367
|
+
const annotation = {
|
|
368
|
+
id,
|
|
369
|
+
label: humanizeIdentifier(id),
|
|
370
|
+
targetObjectId: null,
|
|
371
|
+
body: "",
|
|
372
|
+
tags: [],
|
|
373
|
+
sourceObjectId: null,
|
|
374
|
+
};
|
|
375
|
+
system.annotations.push(annotation);
|
|
376
|
+
annotationIds.add(id);
|
|
377
|
+
return {
|
|
378
|
+
kind: "annotation",
|
|
379
|
+
annotation,
|
|
380
|
+
seenFields: new Set(),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function startGroupSection(tokens, line, groups, groupIds) {
|
|
384
|
+
if (tokens.length !== 2) {
|
|
385
|
+
throw new WorldOrbitError("Invalid group declaration", line, tokens[0]?.column ?? 1);
|
|
386
|
+
}
|
|
387
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
388
|
+
if (!id) {
|
|
389
|
+
throw new WorldOrbitError("Group id must not be empty", line, tokens[1].column);
|
|
390
|
+
}
|
|
391
|
+
if (groupIds.has(id)) {
|
|
392
|
+
throw new WorldOrbitError(`Duplicate group id "${id}"`, line, tokens[1].column);
|
|
393
|
+
}
|
|
394
|
+
const group = {
|
|
395
|
+
id,
|
|
396
|
+
label: humanizeIdentifier(id),
|
|
397
|
+
summary: "",
|
|
398
|
+
color: null,
|
|
399
|
+
tags: [],
|
|
400
|
+
hidden: false,
|
|
401
|
+
};
|
|
402
|
+
groups.push(group);
|
|
403
|
+
groupIds.add(id);
|
|
404
|
+
return {
|
|
405
|
+
kind: "group",
|
|
406
|
+
group,
|
|
407
|
+
seenFields: new Set(),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function startRelationSection(tokens, line, relations, relationIds) {
|
|
411
|
+
if (tokens.length !== 2) {
|
|
412
|
+
throw new WorldOrbitError("Invalid relation declaration", line, tokens[0]?.column ?? 1);
|
|
413
|
+
}
|
|
414
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
415
|
+
if (!id) {
|
|
416
|
+
throw new WorldOrbitError("Relation id must not be empty", line, tokens[1].column);
|
|
417
|
+
}
|
|
418
|
+
if (relationIds.has(id)) {
|
|
419
|
+
throw new WorldOrbitError(`Duplicate relation id "${id}"`, line, tokens[1].column);
|
|
420
|
+
}
|
|
421
|
+
const relation = {
|
|
422
|
+
id,
|
|
423
|
+
from: "",
|
|
424
|
+
to: "",
|
|
425
|
+
kind: "",
|
|
426
|
+
label: null,
|
|
427
|
+
summary: null,
|
|
428
|
+
tags: [],
|
|
429
|
+
color: null,
|
|
430
|
+
hidden: false,
|
|
431
|
+
};
|
|
432
|
+
relations.push(relation);
|
|
433
|
+
relationIds.add(id);
|
|
434
|
+
return {
|
|
435
|
+
kind: "relation",
|
|
436
|
+
relation,
|
|
437
|
+
seenFields: new Set(),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
441
|
+
if (tokens.length !== 2) {
|
|
442
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
443
|
+
}
|
|
444
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
445
|
+
if (!id) {
|
|
446
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
447
|
+
}
|
|
448
|
+
if (eventIds.has(id)) {
|
|
449
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
450
|
+
}
|
|
451
|
+
const event = {
|
|
452
|
+
id,
|
|
453
|
+
kind: "",
|
|
454
|
+
label: humanizeIdentifier(id),
|
|
455
|
+
summary: null,
|
|
456
|
+
targetObjectId: null,
|
|
457
|
+
participantObjectIds: [],
|
|
458
|
+
timing: null,
|
|
459
|
+
visibility: null,
|
|
460
|
+
epoch: null,
|
|
461
|
+
referencePlane: null,
|
|
462
|
+
tags: [],
|
|
463
|
+
color: null,
|
|
464
|
+
hidden: false,
|
|
465
|
+
positions: [],
|
|
466
|
+
};
|
|
467
|
+
const rawPoses = [];
|
|
468
|
+
events.push(event);
|
|
469
|
+
eventPoseNodes.set(id, rawPoses);
|
|
470
|
+
eventIds.add(id);
|
|
471
|
+
return {
|
|
472
|
+
kind: "event",
|
|
473
|
+
event,
|
|
474
|
+
sourceSchemaVersion,
|
|
475
|
+
diagnostics,
|
|
476
|
+
seenFields: new Set(),
|
|
477
|
+
rawPoses,
|
|
478
|
+
inPositions: false,
|
|
479
|
+
positionsIndent: null,
|
|
480
|
+
activePose: null,
|
|
481
|
+
poseIndent: null,
|
|
482
|
+
activePoseSeenFields: new Set(),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
486
|
+
if (tokens.length < 3) {
|
|
487
|
+
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
488
|
+
}
|
|
489
|
+
const objectTypeToken = tokens[1];
|
|
490
|
+
const idToken = tokens[2];
|
|
491
|
+
const objectType = objectTypeToken.value;
|
|
492
|
+
if (!WORLDORBIT_OBJECT_TYPES.has(objectType) || objectType === "system") {
|
|
493
|
+
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
494
|
+
}
|
|
495
|
+
const objectNode = {
|
|
496
|
+
objectType: objectType,
|
|
497
|
+
id: idToken.value,
|
|
498
|
+
fields: parseInlineObjectFields(tokens.slice(3), line, objectType, sourceSchemaVersion, diagnostics),
|
|
499
|
+
infoEntries: [],
|
|
500
|
+
typedBlockEntries: {},
|
|
501
|
+
location: {
|
|
502
|
+
line,
|
|
503
|
+
column: objectTypeToken.column,
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
objectNodes.push(objectNode);
|
|
507
|
+
return {
|
|
508
|
+
kind: "object",
|
|
509
|
+
objectNode,
|
|
510
|
+
sourceSchemaVersion,
|
|
511
|
+
diagnostics,
|
|
512
|
+
activeBlock: null,
|
|
513
|
+
blockIndent: null,
|
|
514
|
+
seenInfoKeys: new Set(),
|
|
515
|
+
seenTypedBlockKeys: {},
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function handleSectionLine(section, indent, tokens, line) {
|
|
519
|
+
switch (section.kind) {
|
|
520
|
+
case "system":
|
|
521
|
+
applySystemField(section, tokens, line);
|
|
522
|
+
return;
|
|
523
|
+
case "defaults":
|
|
524
|
+
applyDefaultsField(section, tokens, line);
|
|
525
|
+
return;
|
|
526
|
+
case "atlas":
|
|
527
|
+
applyAtlasField(section, indent, tokens, line);
|
|
528
|
+
return;
|
|
529
|
+
case "viewpoint":
|
|
530
|
+
applyViewpointField(section, indent, tokens, line);
|
|
531
|
+
return;
|
|
532
|
+
case "annotation":
|
|
533
|
+
applyAnnotationField(section, tokens, line);
|
|
534
|
+
return;
|
|
535
|
+
case "group":
|
|
536
|
+
applyGroupField(section, tokens, line);
|
|
537
|
+
return;
|
|
538
|
+
case "relation":
|
|
539
|
+
applyRelationField(section, tokens, line);
|
|
540
|
+
return;
|
|
541
|
+
case "event":
|
|
542
|
+
applyEventField(section, indent, tokens, line);
|
|
543
|
+
return;
|
|
544
|
+
case "object":
|
|
545
|
+
applyObjectField(section, indent, tokens, line);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function applySystemField(section, tokens, line) {
|
|
550
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
551
|
+
const value = joinFieldValue(tokens, line);
|
|
552
|
+
switch (key) {
|
|
553
|
+
case "title":
|
|
554
|
+
section.system.title = value;
|
|
555
|
+
return;
|
|
556
|
+
case "description":
|
|
557
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
558
|
+
line,
|
|
559
|
+
column: tokens[0].column,
|
|
560
|
+
});
|
|
561
|
+
section.system.description = value;
|
|
562
|
+
return;
|
|
563
|
+
case "epoch":
|
|
564
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
565
|
+
line,
|
|
566
|
+
column: tokens[0].column,
|
|
567
|
+
});
|
|
568
|
+
section.system.epoch = value;
|
|
569
|
+
return;
|
|
570
|
+
case "referenceplane":
|
|
571
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "referencePlane", {
|
|
572
|
+
line,
|
|
573
|
+
column: tokens[0].column,
|
|
574
|
+
});
|
|
575
|
+
section.system.referencePlane = value;
|
|
576
|
+
return;
|
|
577
|
+
default:
|
|
578
|
+
throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function applyDefaultsField(section, tokens, line) {
|
|
582
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
583
|
+
const value = joinFieldValue(tokens, line);
|
|
584
|
+
switch (key) {
|
|
585
|
+
case "view":
|
|
586
|
+
if (isSchema25Projection(value)) {
|
|
587
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
|
|
588
|
+
line,
|
|
589
|
+
column: tokens[0].column,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
593
|
+
return;
|
|
594
|
+
case "scale":
|
|
595
|
+
section.system.defaults.scale = value;
|
|
596
|
+
return;
|
|
597
|
+
case "units":
|
|
598
|
+
section.system.defaults.units = value;
|
|
599
|
+
return;
|
|
600
|
+
case "preset":
|
|
601
|
+
section.system.defaults.preset = parsePresetValue(value, line, tokens[0].column);
|
|
602
|
+
return;
|
|
603
|
+
case "theme":
|
|
604
|
+
section.system.defaults.theme = value;
|
|
605
|
+
return;
|
|
606
|
+
default:
|
|
607
|
+
throw new WorldOrbitError(`Unknown defaults field "${tokens[0].value}"`, line, tokens[0].column);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function applyAtlasField(section, indent, tokens, line) {
|
|
611
|
+
if (section.inMetadata && indent <= (section.metadataIndent ?? 0)) {
|
|
612
|
+
section.inMetadata = false;
|
|
613
|
+
section.metadataIndent = null;
|
|
614
|
+
}
|
|
615
|
+
if (section.inMetadata) {
|
|
616
|
+
const entry = parseInfoLikeEntry(tokens, line, "Invalid atlas metadata entry");
|
|
617
|
+
if (entry.key in section.system.atlasMetadata) {
|
|
618
|
+
throw new WorldOrbitError(`Duplicate atlas metadata key "${entry.key}"`, line, tokens[0].column);
|
|
619
|
+
}
|
|
620
|
+
section.system.atlasMetadata[entry.key] = entry.value;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
|
|
624
|
+
section.inMetadata = true;
|
|
625
|
+
section.metadataIndent = indent;
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
629
|
+
}
|
|
630
|
+
function applyViewpointField(section, indent, tokens, line) {
|
|
631
|
+
if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
|
|
632
|
+
section.inCamera = false;
|
|
633
|
+
section.cameraIndent = null;
|
|
634
|
+
}
|
|
635
|
+
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
636
|
+
section.inFilter = false;
|
|
637
|
+
section.filterIndent = null;
|
|
638
|
+
}
|
|
639
|
+
if (section.inCamera) {
|
|
640
|
+
applyViewpointCameraField(section, tokens, line);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (section.inFilter) {
|
|
644
|
+
applyViewpointFilterField(section, tokens, line);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
|
|
648
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
649
|
+
line,
|
|
650
|
+
column: tokens[0].column,
|
|
651
|
+
});
|
|
652
|
+
if (section.seenFields.has("camera")) {
|
|
653
|
+
throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
|
|
654
|
+
}
|
|
655
|
+
section.seenFields.add("camera");
|
|
656
|
+
section.inCamera = true;
|
|
657
|
+
section.cameraIndent = indent;
|
|
658
|
+
section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera();
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
662
|
+
if (section.seenFields.has("filter")) {
|
|
663
|
+
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
664
|
+
}
|
|
665
|
+
section.seenFields.add("filter");
|
|
666
|
+
section.inFilter = true;
|
|
667
|
+
section.filterIndent = indent;
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
671
|
+
const value = joinFieldValue(tokens, line);
|
|
672
|
+
switch (key) {
|
|
673
|
+
case "label":
|
|
674
|
+
section.viewpoint.label = value;
|
|
675
|
+
return;
|
|
676
|
+
case "summary":
|
|
677
|
+
section.viewpoint.summary = value;
|
|
678
|
+
return;
|
|
679
|
+
case "focus":
|
|
680
|
+
section.viewpoint.focusObjectId = value;
|
|
681
|
+
return;
|
|
682
|
+
case "select":
|
|
683
|
+
section.viewpoint.selectedObjectId = value;
|
|
684
|
+
return;
|
|
685
|
+
case "projection":
|
|
686
|
+
if (isSchema25Projection(value)) {
|
|
687
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
|
|
688
|
+
line,
|
|
689
|
+
column: tokens[0].column,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
693
|
+
return;
|
|
694
|
+
case "preset":
|
|
695
|
+
section.viewpoint.preset = parsePresetValue(value, line, tokens[0].column);
|
|
696
|
+
return;
|
|
697
|
+
case "zoom":
|
|
698
|
+
section.viewpoint.zoom = parsePositiveNumber(value, line, tokens[0].column, "zoom");
|
|
699
|
+
return;
|
|
700
|
+
case "rotation":
|
|
701
|
+
section.viewpoint.rotationDeg = parseFiniteNumber(value, line, tokens[0].column, "rotation");
|
|
702
|
+
return;
|
|
703
|
+
case "camera":
|
|
704
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
705
|
+
line,
|
|
706
|
+
column: tokens[0].column,
|
|
707
|
+
});
|
|
708
|
+
section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
|
|
709
|
+
return;
|
|
710
|
+
case "layers":
|
|
711
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
712
|
+
return;
|
|
713
|
+
case "events":
|
|
714
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
715
|
+
line,
|
|
716
|
+
column: tokens[0].column,
|
|
717
|
+
});
|
|
718
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
719
|
+
return;
|
|
720
|
+
default:
|
|
721
|
+
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function applyViewpointCameraField(section, tokens, line) {
|
|
725
|
+
const key = requireUniqueField(tokens, section.seenCameraFields, line);
|
|
726
|
+
const value = joinFieldValue(tokens, line);
|
|
727
|
+
const camera = section.viewpoint.camera ?? createEmptyViewCamera();
|
|
728
|
+
switch (key) {
|
|
729
|
+
case "azimuth":
|
|
730
|
+
camera.azimuth = parseFiniteNumber(value, line, tokens[0].column, "camera.azimuth");
|
|
731
|
+
break;
|
|
732
|
+
case "elevation":
|
|
733
|
+
camera.elevation = parseFiniteNumber(value, line, tokens[0].column, "camera.elevation");
|
|
734
|
+
break;
|
|
735
|
+
case "roll":
|
|
736
|
+
camera.roll = parseFiniteNumber(value, line, tokens[0].column, "camera.roll");
|
|
737
|
+
break;
|
|
738
|
+
case "distance":
|
|
739
|
+
camera.distance = parsePositiveNumber(value, line, tokens[0].column, "camera.distance");
|
|
740
|
+
break;
|
|
741
|
+
default:
|
|
742
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
|
|
743
|
+
}
|
|
744
|
+
section.viewpoint.camera = camera;
|
|
745
|
+
}
|
|
746
|
+
function applyViewpointFilterField(section, tokens, line) {
|
|
747
|
+
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
748
|
+
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter();
|
|
749
|
+
switch (key) {
|
|
750
|
+
case "query":
|
|
751
|
+
filter.query = joinFieldValue(tokens, line);
|
|
752
|
+
break;
|
|
753
|
+
case "objecttypes":
|
|
754
|
+
filter.objectTypes = parseObjectTypeTokens(tokens.slice(1), line);
|
|
755
|
+
break;
|
|
756
|
+
case "tags":
|
|
757
|
+
filter.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
758
|
+
break;
|
|
759
|
+
case "groups":
|
|
760
|
+
filter.groupIds = parseTokenList(tokens.slice(1), line, "groups");
|
|
761
|
+
break;
|
|
762
|
+
default:
|
|
763
|
+
throw new WorldOrbitError(`Unknown viewpoint filter field "${tokens[0].value}"`, line, tokens[0].column);
|
|
764
|
+
}
|
|
765
|
+
section.viewpoint.filter = filter;
|
|
766
|
+
}
|
|
767
|
+
function applyAnnotationField(section, tokens, line) {
|
|
768
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
769
|
+
switch (key) {
|
|
770
|
+
case "label":
|
|
771
|
+
section.annotation.label = joinFieldValue(tokens, line);
|
|
772
|
+
return;
|
|
773
|
+
case "target":
|
|
774
|
+
section.annotation.targetObjectId = joinFieldValue(tokens, line);
|
|
775
|
+
return;
|
|
776
|
+
case "body":
|
|
777
|
+
section.annotation.body = joinFieldValue(tokens, line);
|
|
778
|
+
return;
|
|
779
|
+
case "tags":
|
|
780
|
+
section.annotation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
781
|
+
return;
|
|
782
|
+
default:
|
|
783
|
+
throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function applyGroupField(section, tokens, line) {
|
|
787
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
788
|
+
switch (key) {
|
|
789
|
+
case "label":
|
|
790
|
+
section.group.label = joinFieldValue(tokens, line);
|
|
791
|
+
return;
|
|
792
|
+
case "summary":
|
|
793
|
+
section.group.summary = joinFieldValue(tokens, line);
|
|
794
|
+
return;
|
|
795
|
+
case "color":
|
|
796
|
+
section.group.color = joinFieldValue(tokens, line);
|
|
797
|
+
return;
|
|
798
|
+
case "tags":
|
|
799
|
+
section.group.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
800
|
+
return;
|
|
801
|
+
case "hidden":
|
|
802
|
+
section.group.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
803
|
+
line,
|
|
804
|
+
column: tokens[0].column,
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
default:
|
|
808
|
+
throw new WorldOrbitError(`Unknown group field "${tokens[0].value}"`, line, tokens[0].column);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function applyRelationField(section, tokens, line) {
|
|
812
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
813
|
+
switch (key) {
|
|
814
|
+
case "from":
|
|
815
|
+
section.relation.from = joinFieldValue(tokens, line);
|
|
816
|
+
return;
|
|
817
|
+
case "to":
|
|
818
|
+
section.relation.to = joinFieldValue(tokens, line);
|
|
819
|
+
return;
|
|
820
|
+
case "kind":
|
|
821
|
+
section.relation.kind = joinFieldValue(tokens, line);
|
|
822
|
+
return;
|
|
823
|
+
case "label":
|
|
824
|
+
section.relation.label = joinFieldValue(tokens, line);
|
|
825
|
+
return;
|
|
826
|
+
case "summary":
|
|
827
|
+
section.relation.summary = joinFieldValue(tokens, line);
|
|
828
|
+
return;
|
|
829
|
+
case "tags":
|
|
830
|
+
section.relation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
831
|
+
return;
|
|
832
|
+
case "color":
|
|
833
|
+
section.relation.color = joinFieldValue(tokens, line);
|
|
834
|
+
return;
|
|
835
|
+
case "hidden":
|
|
836
|
+
section.relation.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
837
|
+
line,
|
|
838
|
+
column: tokens[0].column,
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
default:
|
|
842
|
+
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function applyEventField(section, indent, tokens, line) {
|
|
846
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
847
|
+
section.activePose = null;
|
|
848
|
+
section.poseIndent = null;
|
|
849
|
+
section.activePoseSeenFields.clear();
|
|
850
|
+
}
|
|
851
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
852
|
+
section.inPositions = false;
|
|
853
|
+
section.positionsIndent = null;
|
|
854
|
+
}
|
|
855
|
+
if (section.activePose) {
|
|
856
|
+
if (tokens[0]?.value === "epoch" ||
|
|
857
|
+
tokens[0]?.value === "referencePlane") {
|
|
858
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
|
|
859
|
+
line,
|
|
860
|
+
column: tokens[0]?.column ?? 1,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (section.inPositions) {
|
|
867
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
868
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
869
|
+
}
|
|
870
|
+
const objectId = tokens[1].value;
|
|
871
|
+
if (!objectId.trim()) {
|
|
872
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
873
|
+
}
|
|
874
|
+
const rawPose = {
|
|
875
|
+
objectId,
|
|
876
|
+
fields: [],
|
|
877
|
+
location: { line, column: tokens[0].column },
|
|
878
|
+
};
|
|
879
|
+
section.rawPoses.push(rawPose);
|
|
880
|
+
section.activePose = rawPose;
|
|
881
|
+
section.poseIndent = indent;
|
|
882
|
+
section.activePoseSeenFields = new Set();
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
886
|
+
if (section.seenFields.has("positions")) {
|
|
887
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
888
|
+
}
|
|
889
|
+
section.seenFields.add("positions");
|
|
890
|
+
section.inPositions = true;
|
|
891
|
+
section.positionsIndent = indent;
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
895
|
+
switch (key) {
|
|
896
|
+
case "kind":
|
|
897
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
898
|
+
return;
|
|
899
|
+
case "label":
|
|
900
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
901
|
+
return;
|
|
902
|
+
case "summary":
|
|
903
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
904
|
+
return;
|
|
905
|
+
case "target":
|
|
906
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
907
|
+
return;
|
|
908
|
+
case "participants":
|
|
909
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
910
|
+
return;
|
|
911
|
+
case "timing":
|
|
912
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
913
|
+
return;
|
|
914
|
+
case "visibility":
|
|
915
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
916
|
+
return;
|
|
917
|
+
case "epoch":
|
|
918
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
|
|
919
|
+
line,
|
|
920
|
+
column: tokens[0].column,
|
|
921
|
+
});
|
|
922
|
+
section.event.epoch = joinFieldValue(tokens, line);
|
|
923
|
+
return;
|
|
924
|
+
case "referenceplane":
|
|
925
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
|
|
926
|
+
line,
|
|
927
|
+
column: tokens[0].column,
|
|
928
|
+
});
|
|
929
|
+
section.event.referencePlane = joinFieldValue(tokens, line);
|
|
930
|
+
return;
|
|
931
|
+
case "tags":
|
|
932
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
933
|
+
return;
|
|
934
|
+
case "color":
|
|
935
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
936
|
+
return;
|
|
937
|
+
case "hidden":
|
|
938
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
939
|
+
line,
|
|
940
|
+
column: tokens[0].column,
|
|
941
|
+
});
|
|
942
|
+
return;
|
|
943
|
+
default:
|
|
944
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
948
|
+
if (tokens.length < 2) {
|
|
949
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
950
|
+
}
|
|
951
|
+
const key = tokens[0].value;
|
|
952
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
953
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
954
|
+
}
|
|
955
|
+
if (seenFields.has(key)) {
|
|
956
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
957
|
+
}
|
|
958
|
+
seenFields.add(key);
|
|
959
|
+
return {
|
|
960
|
+
type: "field",
|
|
961
|
+
key,
|
|
962
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
963
|
+
location: { line, column: tokens[0].column },
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function applyObjectField(section, indent, tokens, line) {
|
|
967
|
+
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
968
|
+
section.activeBlock = null;
|
|
969
|
+
section.blockIndent = null;
|
|
970
|
+
}
|
|
971
|
+
if (tokens.length === 1) {
|
|
972
|
+
const blockName = tokens[0].value.toLowerCase();
|
|
973
|
+
if (blockName === "info" || STRUCTURED_TYPED_BLOCKS.has(blockName)) {
|
|
974
|
+
if (blockName !== "info") {
|
|
975
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, blockName, { line, column: tokens[0].column });
|
|
976
|
+
}
|
|
977
|
+
section.activeBlock = blockName;
|
|
978
|
+
section.blockIndent = indent;
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (section.activeBlock) {
|
|
983
|
+
const entry = parseInfoLikeEntry(tokens, line, `Invalid ${section.activeBlock} entry`);
|
|
984
|
+
if (section.activeBlock === "info") {
|
|
985
|
+
if (section.seenInfoKeys.has(entry.key)) {
|
|
986
|
+
throw new WorldOrbitError(`Duplicate info key "${entry.key}"`, line, tokens[0].column);
|
|
987
|
+
}
|
|
988
|
+
section.seenInfoKeys.add(entry.key);
|
|
989
|
+
section.objectNode.infoEntries.push(entry);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const typedBlock = section.activeBlock;
|
|
993
|
+
const seenKeys = section.seenTypedBlockKeys[typedBlock] ??
|
|
994
|
+
(section.seenTypedBlockKeys[typedBlock] = new Set());
|
|
995
|
+
if (seenKeys.has(entry.key)) {
|
|
996
|
+
throw new WorldOrbitError(`Duplicate ${typedBlock} key "${entry.key}"`, line, tokens[0].column);
|
|
997
|
+
}
|
|
998
|
+
seenKeys.add(entry.key);
|
|
999
|
+
const entries = section.objectNode.typedBlockEntries[typedBlock] ??
|
|
1000
|
+
(section.objectNode.typedBlockEntries[typedBlock] = []);
|
|
1001
|
+
entries.push(entry);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
section.objectNode.fields.push(parseObjectField(tokens, line, section.objectNode.objectType, section.sourceSchemaVersion, section.diagnostics));
|
|
1005
|
+
}
|
|
1006
|
+
function requireUniqueField(tokens, seenFields, line) {
|
|
1007
|
+
if (tokens.length < 2) {
|
|
1008
|
+
throw new WorldOrbitError("Invalid atlas field line", line, tokens[0]?.column ?? 1);
|
|
1009
|
+
}
|
|
1010
|
+
const key = tokens[0].value.toLowerCase();
|
|
1011
|
+
if (seenFields.has(key)) {
|
|
1012
|
+
throw new WorldOrbitError(`Duplicate atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
1013
|
+
}
|
|
1014
|
+
seenFields.add(key);
|
|
1015
|
+
return key;
|
|
1016
|
+
}
|
|
1017
|
+
function joinFieldValue(tokens, line) {
|
|
1018
|
+
if (tokens.length < 2) {
|
|
1019
|
+
throw new WorldOrbitError("Missing value for atlas field", line, tokens[0]?.column ?? 1);
|
|
1020
|
+
}
|
|
1021
|
+
return tokens
|
|
1022
|
+
.slice(1)
|
|
1023
|
+
.map((token) => token.value)
|
|
1024
|
+
.join(" ")
|
|
1025
|
+
.trim();
|
|
1026
|
+
}
|
|
1027
|
+
function parseObjectTypeTokens(tokens, line) {
|
|
1028
|
+
return parseTokenList(tokens, line, "objectTypes").filter((value) => value === "star" ||
|
|
1029
|
+
value === "planet" ||
|
|
1030
|
+
value === "moon" ||
|
|
1031
|
+
value === "belt" ||
|
|
1032
|
+
value === "asteroid" ||
|
|
1033
|
+
value === "comet" ||
|
|
1034
|
+
value === "ring" ||
|
|
1035
|
+
value === "structure" ||
|
|
1036
|
+
value === "phenomenon");
|
|
1037
|
+
}
|
|
1038
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
1039
|
+
const layers = {};
|
|
1040
|
+
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
1041
|
+
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
1042
|
+
const raw = token.replace(/^[-!]+/, "").toLowerCase();
|
|
1043
|
+
if (raw === "orbits") {
|
|
1044
|
+
layers["orbits-back"] = enabled;
|
|
1045
|
+
layers["orbits-front"] = enabled;
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (raw === "background" ||
|
|
1049
|
+
raw === "guides" ||
|
|
1050
|
+
raw === "orbits-back" ||
|
|
1051
|
+
raw === "orbits-front" ||
|
|
1052
|
+
raw === "relations" ||
|
|
1053
|
+
raw === "events" ||
|
|
1054
|
+
raw === "objects" ||
|
|
1055
|
+
raw === "labels" ||
|
|
1056
|
+
raw === "metadata") {
|
|
1057
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
1058
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
1059
|
+
line,
|
|
1060
|
+
column: tokens[0]?.column ?? 1,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
layers[raw] = enabled;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return layers;
|
|
1067
|
+
}
|
|
1068
|
+
function parseTokenList(tokens, line, fieldName) {
|
|
1069
|
+
if (tokens.length === 0) {
|
|
1070
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, 1);
|
|
1071
|
+
}
|
|
1072
|
+
const values = tokens.map((token) => token.value).filter(Boolean);
|
|
1073
|
+
if (values.length === 0) {
|
|
1074
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, tokens[0]?.column ?? 1);
|
|
1075
|
+
}
|
|
1076
|
+
return values;
|
|
1077
|
+
}
|
|
1078
|
+
function parseProjectionValue(value, line, column) {
|
|
1079
|
+
const normalized = value.toLowerCase();
|
|
1080
|
+
if (normalized !== "topdown" &&
|
|
1081
|
+
normalized !== "isometric" &&
|
|
1082
|
+
normalized !== "orthographic" &&
|
|
1083
|
+
normalized !== "perspective") {
|
|
1084
|
+
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
1085
|
+
}
|
|
1086
|
+
return normalized;
|
|
1087
|
+
}
|
|
1088
|
+
function isSchema25Projection(value) {
|
|
1089
|
+
const normalized = value.toLowerCase();
|
|
1090
|
+
return normalized === "orthographic" || normalized === "perspective";
|
|
1091
|
+
}
|
|
1092
|
+
function parsePresetValue(value, line, column) {
|
|
1093
|
+
const normalized = value.toLowerCase();
|
|
1094
|
+
if (normalized === "diagram" ||
|
|
1095
|
+
normalized === "presentation" ||
|
|
1096
|
+
normalized === "atlas-card" ||
|
|
1097
|
+
normalized === "markdown") {
|
|
1098
|
+
return normalized;
|
|
1099
|
+
}
|
|
1100
|
+
throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
|
|
1101
|
+
}
|
|
1102
|
+
function parsePositiveNumber(value, line, column, field) {
|
|
1103
|
+
const parsed = parseFiniteNumber(value, line, column, field);
|
|
1104
|
+
if (parsed <= 0) {
|
|
1105
|
+
throw new WorldOrbitError(`Field "${field}" must be greater than zero`, line, column);
|
|
1106
|
+
}
|
|
1107
|
+
return parsed;
|
|
1108
|
+
}
|
|
1109
|
+
function parseFiniteNumber(value, line, column, field) {
|
|
1110
|
+
const parsed = Number(value);
|
|
1111
|
+
if (!Number.isFinite(parsed)) {
|
|
1112
|
+
throw new WorldOrbitError(`Invalid numeric value "${value}" for "${field}"`, line, column);
|
|
1113
|
+
}
|
|
1114
|
+
return parsed;
|
|
1115
|
+
}
|
|
1116
|
+
function createEmptyViewpointFilter() {
|
|
1117
|
+
return {
|
|
1118
|
+
query: null,
|
|
1119
|
+
objectTypes: [],
|
|
1120
|
+
tags: [],
|
|
1121
|
+
groupIds: [],
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function createEmptyViewCamera() {
|
|
1125
|
+
return {
|
|
1126
|
+
azimuth: null,
|
|
1127
|
+
elevation: null,
|
|
1128
|
+
roll: null,
|
|
1129
|
+
distance: null,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function parseInlineViewCamera(tokens, line, current) {
|
|
1133
|
+
if (tokens.length === 0 || tokens.length % 2 !== 0) {
|
|
1134
|
+
throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
|
|
1135
|
+
}
|
|
1136
|
+
const camera = current ? { ...current } : createEmptyViewCamera();
|
|
1137
|
+
const seen = new Set();
|
|
1138
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
1139
|
+
const fieldToken = tokens[index];
|
|
1140
|
+
const valueToken = tokens[index + 1];
|
|
1141
|
+
const key = fieldToken.value.toLowerCase();
|
|
1142
|
+
if (seen.has(key)) {
|
|
1143
|
+
throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
1144
|
+
}
|
|
1145
|
+
seen.add(key);
|
|
1146
|
+
const value = valueToken.value;
|
|
1147
|
+
switch (key) {
|
|
1148
|
+
case "azimuth":
|
|
1149
|
+
camera.azimuth = parseFiniteNumber(value, line, fieldToken.column, "camera.azimuth");
|
|
1150
|
+
break;
|
|
1151
|
+
case "elevation":
|
|
1152
|
+
camera.elevation = parseFiniteNumber(value, line, fieldToken.column, "camera.elevation");
|
|
1153
|
+
break;
|
|
1154
|
+
case "roll":
|
|
1155
|
+
camera.roll = parseFiniteNumber(value, line, fieldToken.column, "camera.roll");
|
|
1156
|
+
break;
|
|
1157
|
+
case "distance":
|
|
1158
|
+
camera.distance = parsePositiveNumber(value, line, fieldToken.column, "camera.distance");
|
|
1159
|
+
break;
|
|
1160
|
+
default:
|
|
1161
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return camera;
|
|
1165
|
+
}
|
|
1166
|
+
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
1167
|
+
const fields = [];
|
|
1168
|
+
let index = 0;
|
|
1169
|
+
while (index < tokens.length) {
|
|
1170
|
+
const keyToken = tokens[index];
|
|
1171
|
+
const spec = getDraftObjectFieldSpec(keyToken.value);
|
|
1172
|
+
if (!spec) {
|
|
1173
|
+
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
1174
|
+
}
|
|
1175
|
+
if (spec.version === "2.1") {
|
|
1176
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
|
|
1177
|
+
line,
|
|
1178
|
+
column: keyToken.column,
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
index++;
|
|
1182
|
+
const valueTokens = [];
|
|
1183
|
+
if (spec.inlineMode === "single") {
|
|
1184
|
+
const nextToken = tokens[index];
|
|
1185
|
+
if (nextToken) {
|
|
1186
|
+
valueTokens.push(nextToken);
|
|
1187
|
+
index++;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
else if (spec.inlineMode === "pair") {
|
|
1191
|
+
for (let count = 0; count < 2; count++) {
|
|
1192
|
+
const nextToken = tokens[index];
|
|
1193
|
+
if (!nextToken) {
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
valueTokens.push(nextToken);
|
|
1197
|
+
index++;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
while (index < tokens.length && !DRAFT_OBJECT_FIELD_KEYS.has(tokens[index].value)) {
|
|
1202
|
+
valueTokens.push(tokens[index]);
|
|
1203
|
+
index++;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (valueTokens.length === 0) {
|
|
1207
|
+
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
1208
|
+
}
|
|
1209
|
+
fields.push({
|
|
1210
|
+
type: "field",
|
|
1211
|
+
key: keyToken.value,
|
|
1212
|
+
values: valueTokens.map((token) => token.value),
|
|
1213
|
+
location: { line, column: keyToken.column },
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
validateDraftObjectFieldCompatibility(fields, objectType);
|
|
1217
|
+
return fields;
|
|
1218
|
+
}
|
|
1219
|
+
function parseObjectField(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
1220
|
+
if (tokens.length < 2) {
|
|
1221
|
+
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
1222
|
+
}
|
|
1223
|
+
const spec = getDraftObjectFieldSpec(tokens[0].value);
|
|
1224
|
+
if (!spec) {
|
|
1225
|
+
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
1226
|
+
}
|
|
1227
|
+
if (spec.version === "2.1") {
|
|
1228
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
|
|
1229
|
+
line,
|
|
1230
|
+
column: tokens[0].column,
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
const field = {
|
|
1234
|
+
type: "field",
|
|
1235
|
+
key: tokens[0].value,
|
|
1236
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
1237
|
+
location: { line, column: tokens[0].column },
|
|
1238
|
+
};
|
|
1239
|
+
validateDraftObjectFieldCompatibility([field], objectType);
|
|
1240
|
+
return field;
|
|
1241
|
+
}
|
|
1242
|
+
function parseInfoLikeEntry(tokens, line, errorMessage) {
|
|
1243
|
+
if (tokens.length < 2) {
|
|
1244
|
+
throw new WorldOrbitError(errorMessage, line, tokens[0]?.column ?? 1);
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
type: "info-entry",
|
|
1248
|
+
key: tokens[0].value,
|
|
1249
|
+
value: tokens.slice(1).map((token) => token.value).join(" "),
|
|
1250
|
+
location: { line, column: tokens[0].column },
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
1254
|
+
const fieldMap = collectDraftFields(node.fields);
|
|
1255
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
1256
|
+
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
1257
|
+
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
1258
|
+
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
1259
|
+
const referencePlane = parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0]);
|
|
1260
|
+
const tidalLock = fieldMap.has("tidalLock")
|
|
1261
|
+
? parseAtlasBoolean(singleFieldValue(fieldMap.get("tidalLock")[0]), "tidalLock", fieldMap.get("tidalLock")[0].location)
|
|
1262
|
+
: undefined;
|
|
1263
|
+
const resonance = fieldMap.has("resonance")
|
|
1264
|
+
? parseResonanceField(fieldMap.get("resonance")[0])
|
|
1265
|
+
: undefined;
|
|
1266
|
+
const renderHints = extractRenderHints(fieldMap);
|
|
1267
|
+
const deriveRules = fieldMap.get("derive")?.map((field) => parseDeriveField(field));
|
|
1268
|
+
const validationRules = fieldMap.get("validate")?.map((field) => ({
|
|
1269
|
+
rule: singleFieldValue(field),
|
|
1270
|
+
}));
|
|
1271
|
+
const lockedFields = fieldMap.has("locked")
|
|
1272
|
+
? [...new Set(fieldMap.get("locked").flatMap((field) => field.values))]
|
|
1273
|
+
: undefined;
|
|
1274
|
+
const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
|
|
1275
|
+
const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
|
|
1276
|
+
const info = normalizeInfoEntries(node.infoEntries, "info");
|
|
1277
|
+
const object = {
|
|
1278
|
+
type: node.objectType,
|
|
1279
|
+
id: node.id,
|
|
1280
|
+
properties,
|
|
1281
|
+
placement,
|
|
1282
|
+
info,
|
|
1283
|
+
};
|
|
1284
|
+
if (groups.length > 0)
|
|
1285
|
+
object.groups = groups;
|
|
1286
|
+
if (epoch)
|
|
1287
|
+
object.epoch = epoch;
|
|
1288
|
+
if (referencePlane)
|
|
1289
|
+
object.referencePlane = referencePlane;
|
|
1290
|
+
if (tidalLock !== undefined)
|
|
1291
|
+
object.tidalLock = tidalLock;
|
|
1292
|
+
if (resonance)
|
|
1293
|
+
object.resonance = resonance;
|
|
1294
|
+
if (renderHints)
|
|
1295
|
+
object.renderHints = renderHints;
|
|
1296
|
+
if (deriveRules?.length)
|
|
1297
|
+
object.deriveRules = deriveRules;
|
|
1298
|
+
if (validationRules?.length)
|
|
1299
|
+
object.validationRules = validationRules;
|
|
1300
|
+
if (lockedFields?.length)
|
|
1301
|
+
object.lockedFields = lockedFields;
|
|
1302
|
+
if (tolerances?.length)
|
|
1303
|
+
object.tolerances = tolerances;
|
|
1304
|
+
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
1305
|
+
object.typedBlocks = typedBlocks;
|
|
1306
|
+
if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
1307
|
+
if (object.groups ||
|
|
1308
|
+
object.epoch ||
|
|
1309
|
+
object.referencePlane ||
|
|
1310
|
+
object.tidalLock !== undefined ||
|
|
1311
|
+
object.resonance ||
|
|
1312
|
+
object.renderHints ||
|
|
1313
|
+
object.deriveRules?.length ||
|
|
1314
|
+
object.validationRules?.length ||
|
|
1315
|
+
object.lockedFields?.length ||
|
|
1316
|
+
object.tolerances?.length ||
|
|
1317
|
+
object.typedBlocks) {
|
|
1318
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return object;
|
|
1322
|
+
}
|
|
1323
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
1324
|
+
return {
|
|
1325
|
+
...event,
|
|
1326
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
1327
|
+
tags: [...new Set(event.tags)],
|
|
1328
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose)),
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function normalizeDraftEventPose(rawPose) {
|
|
1332
|
+
const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
|
|
1333
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
1334
|
+
return {
|
|
1335
|
+
objectId: rawPose.objectId,
|
|
1336
|
+
placement,
|
|
1337
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
1338
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
1339
|
+
epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
|
|
1340
|
+
referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0]),
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function collectDraftFields(fields, _mode = "object") {
|
|
1344
|
+
const grouped = new Map();
|
|
1345
|
+
for (const field of fields) {
|
|
1346
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
1347
|
+
if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
|
|
1348
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
1349
|
+
}
|
|
1350
|
+
if (!spec?.allowRepeat && grouped.has(field.key)) {
|
|
1351
|
+
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
1352
|
+
}
|
|
1353
|
+
const existing = grouped.get(field.key) ?? [];
|
|
1354
|
+
existing.push(field);
|
|
1355
|
+
grouped.set(field.key, existing);
|
|
1356
|
+
}
|
|
1357
|
+
return grouped;
|
|
1358
|
+
}
|
|
1359
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
1360
|
+
const orbitField = fieldMap.get("orbit")?.[0];
|
|
1361
|
+
const atField = fieldMap.get("at")?.[0];
|
|
1362
|
+
const surfaceField = fieldMap.get("surface")?.[0];
|
|
1363
|
+
const freeField = fieldMap.get("free")?.[0];
|
|
1364
|
+
const count = [orbitField, atField, surfaceField, freeField].filter(Boolean).length;
|
|
1365
|
+
if (count > 1) {
|
|
1366
|
+
const conflictingField = orbitField ?? atField ?? surfaceField ?? freeField;
|
|
1367
|
+
throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
|
|
1368
|
+
}
|
|
1369
|
+
if (orbitField) {
|
|
1370
|
+
return {
|
|
1371
|
+
mode: "orbit",
|
|
1372
|
+
target: singleFieldValue(orbitField),
|
|
1373
|
+
distance: parseOptionalUnitField(fieldMap.get("distance")?.[0], "distance"),
|
|
1374
|
+
semiMajor: parseOptionalUnitField(fieldMap.get("semiMajor")?.[0], "semiMajor"),
|
|
1375
|
+
eccentricity: parseOptionalNumberField(fieldMap.get("eccentricity")?.[0], "eccentricity"),
|
|
1376
|
+
period: parseOptionalUnitField(fieldMap.get("period")?.[0], "period"),
|
|
1377
|
+
angle: parseOptionalUnitField(fieldMap.get("angle")?.[0], "angle"),
|
|
1378
|
+
inclination: parseOptionalUnitField(fieldMap.get("inclination")?.[0], "inclination"),
|
|
1379
|
+
phase: parseOptionalUnitField(fieldMap.get("phase")?.[0], "phase"),
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
if (atField) {
|
|
1383
|
+
const target = singleFieldValue(atField);
|
|
1384
|
+
return {
|
|
1385
|
+
mode: "at",
|
|
1386
|
+
target,
|
|
1387
|
+
reference: parseAtlasAtReference(target, atField.location),
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
if (surfaceField) {
|
|
1391
|
+
return {
|
|
1392
|
+
mode: "surface",
|
|
1393
|
+
target: singleFieldValue(surfaceField),
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (freeField) {
|
|
1397
|
+
const raw = singleFieldValue(freeField);
|
|
1398
|
+
const distance = tryParseAtlasUnitValue(raw);
|
|
1399
|
+
return {
|
|
1400
|
+
mode: "free",
|
|
1401
|
+
distance: distance ?? undefined,
|
|
1402
|
+
descriptor: distance ? undefined : raw,
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
function normalizeDraftProperties(objectType, fieldMap) {
|
|
1408
|
+
const properties = {};
|
|
1409
|
+
for (const [key, fields] of fieldMap.entries()) {
|
|
1410
|
+
const field = fields[0];
|
|
1411
|
+
const spec = getDraftObjectFieldSpec(key);
|
|
1412
|
+
if (!field || !spec?.legacySchema || spec.legacySchema.placement) {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
ensureAtlasFieldSupported(key, objectType, field.location);
|
|
1416
|
+
properties[key] = normalizeLegacyScalarValue(key, field.values, field.location);
|
|
1417
|
+
}
|
|
1418
|
+
return properties;
|
|
1419
|
+
}
|
|
1420
|
+
function normalizeInfoEntries(entries, label) {
|
|
1421
|
+
const normalized = {};
|
|
1422
|
+
for (const entry of entries) {
|
|
1423
|
+
if (entry.key in normalized) {
|
|
1424
|
+
throw WorldOrbitError.fromLocation(`Duplicate ${label} key "${entry.key}"`, entry.location);
|
|
1425
|
+
}
|
|
1426
|
+
normalized[entry.key] = entry.value;
|
|
1427
|
+
}
|
|
1428
|
+
return normalized;
|
|
1429
|
+
}
|
|
1430
|
+
function normalizeTypedBlocks(typedBlockEntries) {
|
|
1431
|
+
const typedBlocks = {};
|
|
1432
|
+
for (const blockName of Object.keys(typedBlockEntries)) {
|
|
1433
|
+
const entries = typedBlockEntries[blockName];
|
|
1434
|
+
if (entries?.length) {
|
|
1435
|
+
typedBlocks[blockName] = normalizeInfoEntries(entries, blockName);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return typedBlocks;
|
|
1439
|
+
}
|
|
1440
|
+
function extractRenderHints(fieldMap) {
|
|
1441
|
+
const renderHints = {};
|
|
1442
|
+
const renderLabelField = fieldMap.get("renderLabel")?.[0];
|
|
1443
|
+
const renderOrbitField = fieldMap.get("renderOrbit")?.[0];
|
|
1444
|
+
const renderPriorityField = fieldMap.get("renderPriority")?.[0];
|
|
1445
|
+
if (renderLabelField) {
|
|
1446
|
+
renderHints.renderLabel = parseAtlasBoolean(singleFieldValue(renderLabelField), "renderLabel", renderLabelField.location);
|
|
1447
|
+
}
|
|
1448
|
+
if (renderOrbitField) {
|
|
1449
|
+
renderHints.renderOrbit = parseAtlasBoolean(singleFieldValue(renderOrbitField), "renderOrbit", renderOrbitField.location);
|
|
1450
|
+
}
|
|
1451
|
+
if (renderPriorityField) {
|
|
1452
|
+
renderHints.renderPriority = parseAtlasNumber(singleFieldValue(renderPriorityField), "renderPriority", renderPriorityField.location);
|
|
1453
|
+
}
|
|
1454
|
+
return Object.keys(renderHints).length > 0 ? renderHints : undefined;
|
|
1455
|
+
}
|
|
1456
|
+
function parseResonanceField(field) {
|
|
1457
|
+
if (field.values.length !== 2) {
|
|
1458
|
+
throw WorldOrbitError.fromLocation('Field "resonance" expects "<targetObjectId> <ratio>"', field.location);
|
|
1459
|
+
}
|
|
1460
|
+
const ratio = field.values[1];
|
|
1461
|
+
if (!/^\d+:\d+$/.test(ratio)) {
|
|
1462
|
+
throw WorldOrbitError.fromLocation(`Invalid resonance ratio "${ratio}"`, field.location);
|
|
1463
|
+
}
|
|
1464
|
+
return {
|
|
1465
|
+
targetObjectId: field.values[0],
|
|
1466
|
+
ratio,
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function parseDeriveField(field) {
|
|
1470
|
+
if (field.values.length !== 2) {
|
|
1471
|
+
throw WorldOrbitError.fromLocation('Field "derive" expects "<field> <strategy>"', field.location);
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
field: field.values[0],
|
|
1475
|
+
strategy: field.values[1],
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
function parseToleranceField(field) {
|
|
1479
|
+
if (field.values.length !== 2) {
|
|
1480
|
+
throw WorldOrbitError.fromLocation('Field "tolerance" expects "<field> <value>"', field.location);
|
|
1481
|
+
}
|
|
1482
|
+
const rawValue = field.values[1];
|
|
1483
|
+
const unitValue = tryParseAtlasUnitValue(rawValue);
|
|
1484
|
+
const numericValue = Number(rawValue);
|
|
1485
|
+
return {
|
|
1486
|
+
field: field.values[0],
|
|
1487
|
+
value: unitValue ?? (Number.isFinite(numericValue) ? numericValue : rawValue),
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function parseOptionalTokenList(field) {
|
|
1491
|
+
return field ? [...new Set(field.values)] : [];
|
|
1492
|
+
}
|
|
1493
|
+
function parseOptionalJoinedValue(field) {
|
|
1494
|
+
if (!field) {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
return field.values.join(" ").trim() || null;
|
|
1498
|
+
}
|
|
1499
|
+
function parseOptionalUnitField(field, key) {
|
|
1500
|
+
return field ? parseAtlasUnitValue(singleFieldValue(field), field.location, key) : undefined;
|
|
1501
|
+
}
|
|
1502
|
+
function parseOptionalNumberField(field, key) {
|
|
1503
|
+
return field ? parseAtlasNumber(singleFieldValue(field), key, field.location) : undefined;
|
|
1504
|
+
}
|
|
1505
|
+
function singleFieldValue(field) {
|
|
1506
|
+
return singleAtlasValue(field.values, field.key, field.location);
|
|
1507
|
+
}
|
|
1508
|
+
function getDraftObjectFieldSpec(key) {
|
|
1509
|
+
return DRAFT_OBJECT_FIELD_SPECS.get(key);
|
|
1510
|
+
}
|
|
1511
|
+
function validateDraftObjectFieldCompatibility(fields, objectType) {
|
|
1512
|
+
for (const field of fields) {
|
|
1513
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
1514
|
+
if (!spec) {
|
|
1515
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
1516
|
+
}
|
|
1517
|
+
if (spec.legacySchema) {
|
|
1518
|
+
ensureAtlasFieldSupported(field.key, objectType, field.location);
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if ((field.key === "renderLabel" || field.key === "renderOrbit" || field.key === "tidalLock") &&
|
|
1522
|
+
field.values.length !== 1) {
|
|
1523
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
1528
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
diagnostics.push({
|
|
1532
|
+
code: "parse.schema21.featureCompatibility",
|
|
1533
|
+
severity: "warning",
|
|
1534
|
+
source: "parse",
|
|
1535
|
+
message: `Feature "${featureName}" requires schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
1536
|
+
line: location.line,
|
|
1537
|
+
column: location.column,
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
1541
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
diagnostics.push({
|
|
1545
|
+
code: "parse.schema25.featureCompatibility",
|
|
1546
|
+
severity: "warning",
|
|
1547
|
+
source: "parse",
|
|
1548
|
+
message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
1549
|
+
line: location.line,
|
|
1550
|
+
column: location.column,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
|
|
1554
|
+
return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
|
|
1555
|
+
}
|
|
1556
|
+
function schemaVersionRank(version) {
|
|
1557
|
+
switch (version) {
|
|
1558
|
+
case "2.0-draft":
|
|
1559
|
+
return 0;
|
|
1560
|
+
case "2.0":
|
|
1561
|
+
return 1;
|
|
1562
|
+
case "2.1":
|
|
1563
|
+
return 2;
|
|
1564
|
+
case "2.5":
|
|
1565
|
+
return 3;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function preprocessAtlasSource(source) {
|
|
1569
|
+
const chars = [...source];
|
|
1570
|
+
const comments = [];
|
|
1571
|
+
let inString = false;
|
|
1572
|
+
let inBlockComment = false;
|
|
1573
|
+
let blockCommentStart = null;
|
|
1574
|
+
let line = 1;
|
|
1575
|
+
let column = 1;
|
|
1576
|
+
for (let index = 0; index < chars.length; index++) {
|
|
1577
|
+
const ch = chars[index];
|
|
1578
|
+
const next = chars[index + 1];
|
|
1579
|
+
if (inBlockComment) {
|
|
1580
|
+
if (ch === "*" && next === "/") {
|
|
1581
|
+
chars[index] = " ";
|
|
1582
|
+
chars[index + 1] = " ";
|
|
1583
|
+
inBlockComment = false;
|
|
1584
|
+
blockCommentStart = null;
|
|
1585
|
+
index++;
|
|
1586
|
+
column += 2;
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
1590
|
+
chars[index] = " ";
|
|
1591
|
+
}
|
|
1592
|
+
if (ch === "\n") {
|
|
1593
|
+
line++;
|
|
1594
|
+
column = 1;
|
|
1595
|
+
}
|
|
1596
|
+
else {
|
|
1597
|
+
column++;
|
|
1598
|
+
}
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (!inString && ch === "/" && next === "*") {
|
|
1602
|
+
comments.push({ kind: "block", line, column });
|
|
1603
|
+
chars[index] = " ";
|
|
1604
|
+
chars[index + 1] = " ";
|
|
1605
|
+
inBlockComment = true;
|
|
1606
|
+
blockCommentStart = { line, column };
|
|
1607
|
+
index++;
|
|
1608
|
+
column += 2;
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
if (!inString && ch === "#" && !isHexColorLiteral(chars, index)) {
|
|
1612
|
+
comments.push({ kind: "line", line, column });
|
|
1613
|
+
chars[index] = " ";
|
|
1614
|
+
let inner = index + 1;
|
|
1615
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
1616
|
+
chars[inner] = " ";
|
|
1617
|
+
inner++;
|
|
1618
|
+
}
|
|
1619
|
+
column += inner - index;
|
|
1620
|
+
index = inner - 1;
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
1624
|
+
inString = !inString;
|
|
1625
|
+
}
|
|
1626
|
+
if (ch === "\n") {
|
|
1627
|
+
line++;
|
|
1628
|
+
column = 1;
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
column++;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
if (inBlockComment) {
|
|
1635
|
+
throw WorldOrbitError.fromLocation("Unclosed block comment", blockCommentStart ?? undefined);
|
|
1636
|
+
}
|
|
1637
|
+
return {
|
|
1638
|
+
source: chars.join(""),
|
|
1639
|
+
comments,
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function isHexColorLiteral(chars, start) {
|
|
1643
|
+
let index = start + 1;
|
|
1644
|
+
let length = 0;
|
|
1645
|
+
while (index < chars.length && /[0-9a-f]/i.test(chars[index] ?? "")) {
|
|
1646
|
+
index++;
|
|
1647
|
+
length++;
|
|
1648
|
+
}
|
|
1649
|
+
if (![3, 4, 6, 8].includes(length)) {
|
|
1650
|
+
return false;
|
|
1651
|
+
}
|
|
1652
|
+
const next = chars[index];
|
|
1653
|
+
return next === undefined || next === " " || next === "\t" || next === "\r" || next === "\n";
|
|
1654
|
+
}
|