worldorbit 2.5.2
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/LICENSE.md +5 -0
- package/README.md +250 -0
- package/dist/browser/core/dist/index.js +4009 -0
- package/dist/browser/markdown/dist/index.js +3951 -0
- package/dist/browser/viewer/dist/index.js +5981 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +84 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +16 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +25 -0
- package/dist/normalize.d.ts +2 -0
- package/dist/normalize.js +243 -0
- package/dist/parse.d.ts +2 -0
- package/dist/parse.js +126 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +683 -0
- package/dist/tokenize.d.ts +4 -0
- package/dist/tokenize.js +68 -0
- package/dist/types.d.ts +208 -0
- package/dist/types.js +1 -0
- package/dist/unpkg/core/dist/index.js +4081 -0
- package/dist/unpkg/markdown/dist/index.js +3979 -0
- package/dist/unpkg/test.html +1 -0
- package/dist/unpkg/viewer/dist/index.js +6038 -0
- package/dist/unpkg/worldorbit-core.min.js +5 -0
- package/dist/unpkg/worldorbit-markdown.min.js +81 -0
- package/dist/unpkg/worldorbit-viewer.min.js +232 -0
- package/dist/unpkg/worldorbit.d.ts +2 -0
- package/dist/unpkg/worldorbit.js +2 -0
- package/dist/unpkg/worldorbit.min.js +236 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +31 -0
- package/dist/viewer-state.d.ts +16 -0
- package/dist/viewer-state.js +130 -0
- package/dist/viewer.d.ts +2 -0
- package/dist/viewer.js +434 -0
- package/package.json +64 -0
- package/packages/core/README.md +13 -0
- package/packages/core/dist/atlas-edit.d.ts +11 -0
- package/packages/core/dist/atlas-edit.js +210 -0
- package/packages/core/dist/diagnostics.d.ts +10 -0
- package/packages/core/dist/diagnostics.js +109 -0
- package/packages/core/dist/draft-parse.d.ts +3 -0
- package/packages/core/dist/draft-parse.js +642 -0
- package/packages/core/dist/draft.d.ts +15 -0
- package/packages/core/dist/draft.js +343 -0
- package/packages/core/dist/errors.d.ts +7 -0
- package/packages/core/dist/errors.js +16 -0
- package/packages/core/dist/format.d.ts +4 -0
- package/packages/core/dist/format.js +364 -0
- package/packages/core/dist/index.d.ts +28 -0
- package/packages/core/dist/index.js +44 -0
- package/packages/core/dist/load.d.ts +4 -0
- package/packages/core/dist/load.js +130 -0
- package/packages/core/dist/markdown.d.ts +2 -0
- package/packages/core/dist/markdown.js +37 -0
- package/packages/core/dist/normalize.d.ts +2 -0
- package/packages/core/dist/normalize.js +304 -0
- package/packages/core/dist/parse.d.ts +2 -0
- package/packages/core/dist/parse.js +133 -0
- package/packages/core/dist/scene.d.ts +3 -0
- package/packages/core/dist/scene.js +1484 -0
- package/packages/core/dist/schema.d.ts +8 -0
- package/packages/core/dist/schema.js +298 -0
- package/packages/core/dist/tokenize.d.ts +4 -0
- package/packages/core/dist/tokenize.js +68 -0
- package/packages/core/dist/types.d.ts +382 -0
- package/packages/core/dist/types.js +1 -0
- package/packages/core/dist/validate.d.ts +2 -0
- package/packages/core/dist/validate.js +56 -0
- package/packages/editor/dist/editor.d.ts +2 -0
- package/packages/editor/dist/editor.js +2620 -0
- package/packages/editor/dist/index.d.ts +2 -0
- package/packages/editor/dist/index.js +1 -0
- package/packages/editor/dist/types.d.ts +53 -0
- package/packages/editor/dist/types.js +1 -0
- package/packages/markdown/README.md +9 -0
- package/packages/markdown/dist/html.d.ts +3 -0
- package/packages/markdown/dist/html.js +57 -0
- package/packages/markdown/dist/index.d.ts +4 -0
- package/packages/markdown/dist/index.js +3 -0
- package/packages/markdown/dist/rehype.d.ts +10 -0
- package/packages/markdown/dist/rehype.js +49 -0
- package/packages/markdown/dist/remark.d.ts +9 -0
- package/packages/markdown/dist/remark.js +28 -0
- package/packages/markdown/dist/types.d.ts +11 -0
- package/packages/markdown/dist/types.js +1 -0
- package/packages/viewer/README.md +12 -0
- package/packages/viewer/dist/atlas-state.d.ts +12 -0
- package/packages/viewer/dist/atlas-state.js +251 -0
- package/packages/viewer/dist/atlas-viewer.d.ts +2 -0
- package/packages/viewer/dist/atlas-viewer.js +448 -0
- package/packages/viewer/dist/custom-element.d.ts +1 -0
- package/packages/viewer/dist/custom-element.js +64 -0
- package/packages/viewer/dist/embed.d.ts +20 -0
- package/packages/viewer/dist/embed.js +138 -0
- package/packages/viewer/dist/index.d.ts +9 -0
- package/packages/viewer/dist/index.js +8 -0
- package/packages/viewer/dist/minimap.d.ts +3 -0
- package/packages/viewer/dist/minimap.js +63 -0
- package/packages/viewer/dist/render.d.ts +6 -0
- package/packages/viewer/dist/render.js +585 -0
- package/packages/viewer/dist/theme.d.ts +4 -0
- package/packages/viewer/dist/theme.js +98 -0
- package/packages/viewer/dist/tooltip.d.ts +3 -0
- package/packages/viewer/dist/tooltip.js +154 -0
- package/packages/viewer/dist/types.d.ts +256 -0
- package/packages/viewer/dist/types.js +1 -0
- package/packages/viewer/dist/viewer-state.d.ts +19 -0
- package/packages/viewer/dist/viewer-state.js +162 -0
- package/packages/viewer/dist/viewer.d.ts +2 -0
- package/packages/viewer/dist/viewer.js +1156 -0
|
@@ -0,0 +1,4081 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var WorldOrbit = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// packages/core/dist/index.js
|
|
22
|
+
var dist_exports = {};
|
|
23
|
+
__export(dist_exports, {
|
|
24
|
+
WORLDORBIT_FIELD_KEYS: () => WORLDORBIT_FIELD_KEYS,
|
|
25
|
+
WORLDORBIT_FIELD_SCHEMAS: () => WORLDORBIT_FIELD_SCHEMAS,
|
|
26
|
+
WORLDORBIT_OBJECT_TYPES: () => WORLDORBIT_OBJECT_TYPES,
|
|
27
|
+
WorldOrbitError: () => WorldOrbitError,
|
|
28
|
+
cloneAtlasDocument: () => cloneAtlasDocument,
|
|
29
|
+
createDiagnostic: () => createDiagnostic,
|
|
30
|
+
createEmptyAtlasDocument: () => createEmptyAtlasDocument,
|
|
31
|
+
detectWorldOrbitSchemaVersion: () => detectWorldOrbitSchemaVersion,
|
|
32
|
+
diagnosticFromError: () => diagnosticFromError,
|
|
33
|
+
extractWorldOrbitBlocks: () => extractWorldOrbitBlocks,
|
|
34
|
+
formatAtlasDocument: () => formatAtlasDocument,
|
|
35
|
+
formatDocument: () => formatDocument,
|
|
36
|
+
formatDraftDocument: () => formatDraftDocument,
|
|
37
|
+
getAtlasDocumentNode: () => getAtlasDocumentNode,
|
|
38
|
+
getFieldSchema: () => getFieldSchema,
|
|
39
|
+
isKnownFieldKey: () => isKnownFieldKey,
|
|
40
|
+
listAtlasDocumentPaths: () => listAtlasDocumentPaths,
|
|
41
|
+
load: () => load,
|
|
42
|
+
loadWorldOrbitSource: () => loadWorldOrbitSource,
|
|
43
|
+
loadWorldOrbitSourceWithDiagnostics: () => loadWorldOrbitSourceWithDiagnostics,
|
|
44
|
+
materializeAtlasDocument: () => materializeAtlasDocument,
|
|
45
|
+
materializeDraftDocument: () => materializeDraftDocument,
|
|
46
|
+
normalizeDocument: () => normalizeDocument,
|
|
47
|
+
normalizeWithDiagnostics: () => normalizeWithDiagnostics,
|
|
48
|
+
parse: () => parse,
|
|
49
|
+
parseSafe: () => parseSafe,
|
|
50
|
+
parseWithDiagnostics: () => parseWithDiagnostics,
|
|
51
|
+
parseWorldOrbit: () => parseWorldOrbit,
|
|
52
|
+
parseWorldOrbitAtlas: () => parseWorldOrbitAtlas,
|
|
53
|
+
parseWorldOrbitDraft: () => parseWorldOrbitDraft,
|
|
54
|
+
removeAtlasDocumentNode: () => removeAtlasDocumentNode,
|
|
55
|
+
render: () => render,
|
|
56
|
+
renderDocumentToScene: () => renderDocumentToScene,
|
|
57
|
+
resolveAtlasDiagnosticPath: () => resolveAtlasDiagnosticPath,
|
|
58
|
+
resolveAtlasDiagnostics: () => resolveAtlasDiagnostics,
|
|
59
|
+
rotatePoint: () => rotatePoint,
|
|
60
|
+
stringify: () => stringify,
|
|
61
|
+
supportsObjectType: () => supportsObjectType,
|
|
62
|
+
tokenizeLine: () => tokenizeLine,
|
|
63
|
+
tokenizeLineDetailed: () => tokenizeLineDetailed,
|
|
64
|
+
unitFamilyAllowsUnit: () => unitFamilyAllowsUnit,
|
|
65
|
+
updateAtlasDocumentNode: () => updateAtlasDocumentNode,
|
|
66
|
+
upgradeDocumentToDraftV2: () => upgradeDocumentToDraftV2,
|
|
67
|
+
upgradeDocumentToV2: () => upgradeDocumentToV2,
|
|
68
|
+
upsertAtlasDocumentNode: () => upsertAtlasDocumentNode,
|
|
69
|
+
validateAtlasDocumentWithDiagnostics: () => validateAtlasDocumentWithDiagnostics,
|
|
70
|
+
validateDocument: () => validateDocument,
|
|
71
|
+
validateDocumentWithDiagnostics: () => validateDocumentWithDiagnostics
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// packages/core/dist/errors.js
|
|
75
|
+
var WorldOrbitError = class _WorldOrbitError extends Error {
|
|
76
|
+
line;
|
|
77
|
+
column;
|
|
78
|
+
constructor(message, line, column) {
|
|
79
|
+
const locationSuffix = line === void 0 ? "" : ` (line ${line}${column === void 0 ? "" : `, column ${column}`})`;
|
|
80
|
+
super(`${message}${locationSuffix}`);
|
|
81
|
+
this.name = "WorldOrbitError";
|
|
82
|
+
this.line = line;
|
|
83
|
+
this.column = column;
|
|
84
|
+
}
|
|
85
|
+
static fromLocation(message, location) {
|
|
86
|
+
return new _WorldOrbitError(message, location?.line, location?.column);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// packages/core/dist/schema.js
|
|
91
|
+
var ALL_OBJECTS = [
|
|
92
|
+
"system",
|
|
93
|
+
"star",
|
|
94
|
+
"planet",
|
|
95
|
+
"moon",
|
|
96
|
+
"belt",
|
|
97
|
+
"asteroid",
|
|
98
|
+
"comet",
|
|
99
|
+
"ring",
|
|
100
|
+
"structure",
|
|
101
|
+
"phenomenon"
|
|
102
|
+
];
|
|
103
|
+
var NON_SYSTEM_OBJECTS = ALL_OBJECTS.filter((objectType) => objectType !== "system");
|
|
104
|
+
var IMAGE_OBJECTS = [
|
|
105
|
+
"star",
|
|
106
|
+
"planet",
|
|
107
|
+
"moon",
|
|
108
|
+
"asteroid",
|
|
109
|
+
"comet",
|
|
110
|
+
"structure",
|
|
111
|
+
"phenomenon"
|
|
112
|
+
];
|
|
113
|
+
var ANCHORED_OBJECTS = ["structure", "phenomenon"];
|
|
114
|
+
var ORBITAL_OBJECTS = [
|
|
115
|
+
"star",
|
|
116
|
+
"planet",
|
|
117
|
+
"moon",
|
|
118
|
+
"belt",
|
|
119
|
+
"asteroid",
|
|
120
|
+
"comet",
|
|
121
|
+
"ring",
|
|
122
|
+
"structure",
|
|
123
|
+
"phenomenon"
|
|
124
|
+
];
|
|
125
|
+
var FREE_OBJECTS = [
|
|
126
|
+
"star",
|
|
127
|
+
"planet",
|
|
128
|
+
"moon",
|
|
129
|
+
"belt",
|
|
130
|
+
"asteroid",
|
|
131
|
+
"comet",
|
|
132
|
+
"ring",
|
|
133
|
+
"structure",
|
|
134
|
+
"phenomenon"
|
|
135
|
+
];
|
|
136
|
+
function createField(key, options) {
|
|
137
|
+
return {
|
|
138
|
+
key,
|
|
139
|
+
...options
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
var WORLDORBIT_OBJECT_TYPES = new Set(ALL_OBJECTS);
|
|
143
|
+
var WORLDORBIT_FIELD_SCHEMAS = new Map([
|
|
144
|
+
createField("orbit", {
|
|
145
|
+
kind: "string",
|
|
146
|
+
placement: true,
|
|
147
|
+
arity: "single",
|
|
148
|
+
objectTypes: ORBITAL_OBJECTS
|
|
149
|
+
}),
|
|
150
|
+
createField("distance", {
|
|
151
|
+
kind: "unit",
|
|
152
|
+
placement: true,
|
|
153
|
+
arity: "single",
|
|
154
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
155
|
+
unitFamily: "distance"
|
|
156
|
+
}),
|
|
157
|
+
createField("semiMajor", {
|
|
158
|
+
kind: "unit",
|
|
159
|
+
placement: true,
|
|
160
|
+
arity: "single",
|
|
161
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
162
|
+
unitFamily: "distance"
|
|
163
|
+
}),
|
|
164
|
+
createField("eccentricity", {
|
|
165
|
+
kind: "number",
|
|
166
|
+
placement: true,
|
|
167
|
+
arity: "single",
|
|
168
|
+
objectTypes: ORBITAL_OBJECTS
|
|
169
|
+
}),
|
|
170
|
+
createField("period", {
|
|
171
|
+
kind: "unit",
|
|
172
|
+
placement: true,
|
|
173
|
+
arity: "single",
|
|
174
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
175
|
+
unitFamily: "duration"
|
|
176
|
+
}),
|
|
177
|
+
createField("angle", {
|
|
178
|
+
kind: "unit",
|
|
179
|
+
placement: true,
|
|
180
|
+
arity: "single",
|
|
181
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
182
|
+
unitFamily: "angle"
|
|
183
|
+
}),
|
|
184
|
+
createField("inclination", {
|
|
185
|
+
kind: "unit",
|
|
186
|
+
placement: true,
|
|
187
|
+
arity: "single",
|
|
188
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
189
|
+
unitFamily: "angle"
|
|
190
|
+
}),
|
|
191
|
+
createField("phase", {
|
|
192
|
+
kind: "unit",
|
|
193
|
+
placement: true,
|
|
194
|
+
arity: "single",
|
|
195
|
+
objectTypes: ORBITAL_OBJECTS,
|
|
196
|
+
unitFamily: "angle"
|
|
197
|
+
}),
|
|
198
|
+
createField("at", {
|
|
199
|
+
kind: "string",
|
|
200
|
+
placement: true,
|
|
201
|
+
arity: "single",
|
|
202
|
+
objectTypes: ANCHORED_OBJECTS
|
|
203
|
+
}),
|
|
204
|
+
createField("surface", {
|
|
205
|
+
kind: "string",
|
|
206
|
+
placement: true,
|
|
207
|
+
arity: "single",
|
|
208
|
+
objectTypes: ANCHORED_OBJECTS
|
|
209
|
+
}),
|
|
210
|
+
createField("free", {
|
|
211
|
+
kind: "string",
|
|
212
|
+
placement: true,
|
|
213
|
+
arity: "single",
|
|
214
|
+
objectTypes: FREE_OBJECTS
|
|
215
|
+
}),
|
|
216
|
+
createField("kind", {
|
|
217
|
+
kind: "string",
|
|
218
|
+
placement: false,
|
|
219
|
+
arity: "single",
|
|
220
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
221
|
+
}),
|
|
222
|
+
createField("class", {
|
|
223
|
+
kind: "string",
|
|
224
|
+
placement: false,
|
|
225
|
+
arity: "single",
|
|
226
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
227
|
+
}),
|
|
228
|
+
createField("culture", {
|
|
229
|
+
kind: "string",
|
|
230
|
+
placement: false,
|
|
231
|
+
arity: "single",
|
|
232
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
233
|
+
}),
|
|
234
|
+
createField("tags", {
|
|
235
|
+
kind: "list",
|
|
236
|
+
placement: false,
|
|
237
|
+
arity: "multiple",
|
|
238
|
+
objectTypes: ALL_OBJECTS
|
|
239
|
+
}),
|
|
240
|
+
createField("color", {
|
|
241
|
+
kind: "string",
|
|
242
|
+
placement: false,
|
|
243
|
+
arity: "single",
|
|
244
|
+
objectTypes: ALL_OBJECTS
|
|
245
|
+
}),
|
|
246
|
+
createField("image", {
|
|
247
|
+
kind: "string",
|
|
248
|
+
placement: false,
|
|
249
|
+
arity: "single",
|
|
250
|
+
objectTypes: IMAGE_OBJECTS
|
|
251
|
+
}),
|
|
252
|
+
createField("hidden", {
|
|
253
|
+
kind: "boolean",
|
|
254
|
+
placement: false,
|
|
255
|
+
arity: "single",
|
|
256
|
+
objectTypes: ALL_OBJECTS
|
|
257
|
+
}),
|
|
258
|
+
createField("radius", {
|
|
259
|
+
kind: "unit",
|
|
260
|
+
placement: false,
|
|
261
|
+
arity: "single",
|
|
262
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
263
|
+
unitFamily: "radius"
|
|
264
|
+
}),
|
|
265
|
+
createField("mass", {
|
|
266
|
+
kind: "unit",
|
|
267
|
+
placement: false,
|
|
268
|
+
arity: "single",
|
|
269
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
270
|
+
unitFamily: "mass"
|
|
271
|
+
}),
|
|
272
|
+
createField("density", {
|
|
273
|
+
kind: "unit",
|
|
274
|
+
placement: false,
|
|
275
|
+
arity: "single",
|
|
276
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
277
|
+
unitFamily: "generic"
|
|
278
|
+
}),
|
|
279
|
+
createField("gravity", {
|
|
280
|
+
kind: "unit",
|
|
281
|
+
placement: false,
|
|
282
|
+
arity: "single",
|
|
283
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
284
|
+
unitFamily: "generic"
|
|
285
|
+
}),
|
|
286
|
+
createField("temperature", {
|
|
287
|
+
kind: "unit",
|
|
288
|
+
placement: false,
|
|
289
|
+
arity: "single",
|
|
290
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
291
|
+
unitFamily: "generic"
|
|
292
|
+
}),
|
|
293
|
+
createField("albedo", {
|
|
294
|
+
kind: "number",
|
|
295
|
+
placement: false,
|
|
296
|
+
arity: "single",
|
|
297
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
298
|
+
}),
|
|
299
|
+
createField("atmosphere", {
|
|
300
|
+
kind: "string",
|
|
301
|
+
placement: false,
|
|
302
|
+
arity: "single",
|
|
303
|
+
objectTypes: ["planet", "moon", "asteroid", "comet", "phenomenon"]
|
|
304
|
+
}),
|
|
305
|
+
createField("inner", {
|
|
306
|
+
kind: "unit",
|
|
307
|
+
placement: false,
|
|
308
|
+
arity: "single",
|
|
309
|
+
objectTypes: ["belt", "ring", "phenomenon"],
|
|
310
|
+
unitFamily: "distance"
|
|
311
|
+
}),
|
|
312
|
+
createField("outer", {
|
|
313
|
+
kind: "unit",
|
|
314
|
+
placement: false,
|
|
315
|
+
arity: "single",
|
|
316
|
+
objectTypes: ["belt", "ring", "phenomenon"],
|
|
317
|
+
unitFamily: "distance"
|
|
318
|
+
}),
|
|
319
|
+
createField("view", {
|
|
320
|
+
kind: "string",
|
|
321
|
+
placement: false,
|
|
322
|
+
arity: "single",
|
|
323
|
+
objectTypes: ["system"]
|
|
324
|
+
}),
|
|
325
|
+
createField("scale", {
|
|
326
|
+
kind: "string",
|
|
327
|
+
placement: false,
|
|
328
|
+
arity: "single",
|
|
329
|
+
objectTypes: ["system"]
|
|
330
|
+
}),
|
|
331
|
+
createField("units", {
|
|
332
|
+
kind: "string",
|
|
333
|
+
placement: false,
|
|
334
|
+
arity: "single",
|
|
335
|
+
objectTypes: ["system"]
|
|
336
|
+
}),
|
|
337
|
+
createField("title", {
|
|
338
|
+
kind: "string",
|
|
339
|
+
placement: false,
|
|
340
|
+
arity: "single",
|
|
341
|
+
objectTypes: ["system"]
|
|
342
|
+
}),
|
|
343
|
+
createField("on", {
|
|
344
|
+
kind: "string",
|
|
345
|
+
placement: false,
|
|
346
|
+
arity: "single",
|
|
347
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
348
|
+
}),
|
|
349
|
+
createField("source", {
|
|
350
|
+
kind: "string",
|
|
351
|
+
placement: false,
|
|
352
|
+
arity: "single",
|
|
353
|
+
objectTypes: NON_SYSTEM_OBJECTS
|
|
354
|
+
}),
|
|
355
|
+
createField("cycle", {
|
|
356
|
+
kind: "unit",
|
|
357
|
+
placement: false,
|
|
358
|
+
arity: "single",
|
|
359
|
+
objectTypes: NON_SYSTEM_OBJECTS,
|
|
360
|
+
unitFamily: "duration"
|
|
361
|
+
})
|
|
362
|
+
].map((schema) => [schema.key, schema]));
|
|
363
|
+
var WORLDORBIT_FIELD_KEYS = new Set(WORLDORBIT_FIELD_SCHEMAS.keys());
|
|
364
|
+
function getFieldSchema(key) {
|
|
365
|
+
return WORLDORBIT_FIELD_SCHEMAS.get(key);
|
|
366
|
+
}
|
|
367
|
+
function isKnownFieldKey(key) {
|
|
368
|
+
return WORLDORBIT_FIELD_KEYS.has(key);
|
|
369
|
+
}
|
|
370
|
+
function supportsObjectType(schema, objectType) {
|
|
371
|
+
return schema.objectTypes.includes(objectType);
|
|
372
|
+
}
|
|
373
|
+
function unitFamilyAllowsUnit(family, unit) {
|
|
374
|
+
switch (family) {
|
|
375
|
+
case "distance":
|
|
376
|
+
return unit === null || ["au", "km", "re", "sol"].includes(unit);
|
|
377
|
+
case "radius":
|
|
378
|
+
return unit === null || ["km", "re", "sol"].includes(unit);
|
|
379
|
+
case "mass":
|
|
380
|
+
return unit === null || ["me", "sol"].includes(unit);
|
|
381
|
+
case "duration":
|
|
382
|
+
return unit === null || ["h", "d", "y"].includes(unit);
|
|
383
|
+
case "angle":
|
|
384
|
+
return unit === null || unit === "deg";
|
|
385
|
+
case "generic":
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// packages/core/dist/tokenize.js
|
|
391
|
+
function tokenizeLine(input) {
|
|
392
|
+
return tokenizeLineDetailed(input).map((token) => token.value);
|
|
393
|
+
}
|
|
394
|
+
function tokenizeLineDetailed(input, options = {}) {
|
|
395
|
+
const tokens = [];
|
|
396
|
+
const columnOffset = options.columnOffset ?? 0;
|
|
397
|
+
let current = "";
|
|
398
|
+
let tokenColumn = null;
|
|
399
|
+
let tokenWasQuoted = false;
|
|
400
|
+
let inQuotes = false;
|
|
401
|
+
let quoteColumn = null;
|
|
402
|
+
const pushToken = () => {
|
|
403
|
+
if (tokenColumn === null) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
tokens.push({
|
|
407
|
+
value: current,
|
|
408
|
+
column: tokenColumn,
|
|
409
|
+
quoted: tokenWasQuoted
|
|
410
|
+
});
|
|
411
|
+
current = "";
|
|
412
|
+
tokenColumn = null;
|
|
413
|
+
tokenWasQuoted = false;
|
|
414
|
+
};
|
|
415
|
+
for (let index = 0; index < input.length; index++) {
|
|
416
|
+
const ch = input[index];
|
|
417
|
+
const absoluteColumn = columnOffset + index + 1;
|
|
418
|
+
if (inQuotes && ch === "\\") {
|
|
419
|
+
const next = input[index + 1];
|
|
420
|
+
if (next === '"' || next === "\\") {
|
|
421
|
+
current += next;
|
|
422
|
+
index++;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (ch === '"') {
|
|
427
|
+
if (!inQuotes) {
|
|
428
|
+
if (tokenColumn === null) {
|
|
429
|
+
tokenColumn = absoluteColumn;
|
|
430
|
+
}
|
|
431
|
+
tokenWasQuoted = true;
|
|
432
|
+
quoteColumn = absoluteColumn;
|
|
433
|
+
inQuotes = true;
|
|
434
|
+
} else {
|
|
435
|
+
inQuotes = false;
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (!inQuotes && /\s/.test(ch)) {
|
|
440
|
+
pushToken();
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (tokenColumn === null) {
|
|
444
|
+
tokenColumn = absoluteColumn;
|
|
445
|
+
}
|
|
446
|
+
current += ch;
|
|
447
|
+
}
|
|
448
|
+
if (inQuotes) {
|
|
449
|
+
throw new WorldOrbitError("Unclosed quote in line", options.line, quoteColumn ?? columnOffset + input.length);
|
|
450
|
+
}
|
|
451
|
+
pushToken();
|
|
452
|
+
return tokens;
|
|
453
|
+
}
|
|
454
|
+
function getIndent(rawLine) {
|
|
455
|
+
return rawLine.match(/^\s*/)?.[0].length ?? 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// packages/core/dist/parse.js
|
|
459
|
+
function parseWorldOrbit(source) {
|
|
460
|
+
const lines = source.split(/\r?\n/);
|
|
461
|
+
const objects = [];
|
|
462
|
+
let currentObject = null;
|
|
463
|
+
let inInfoBlock = false;
|
|
464
|
+
let infoIndent = null;
|
|
465
|
+
for (let index = 0; index < lines.length; index++) {
|
|
466
|
+
const rawLine = lines[index];
|
|
467
|
+
const lineNumber = index + 1;
|
|
468
|
+
if (!rawLine.trim()) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const indent = getIndent(rawLine);
|
|
472
|
+
const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
|
|
473
|
+
line: lineNumber,
|
|
474
|
+
columnOffset: indent
|
|
475
|
+
});
|
|
476
|
+
if (tokens.length === 0) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (indent === 0) {
|
|
480
|
+
inInfoBlock = false;
|
|
481
|
+
infoIndent = null;
|
|
482
|
+
const objectNode = parseObjectHeader(tokens, lineNumber);
|
|
483
|
+
objects.push(objectNode);
|
|
484
|
+
currentObject = objectNode;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (!currentObject) {
|
|
488
|
+
throw new WorldOrbitError("Indented line without parent object", lineNumber, indent + 1);
|
|
489
|
+
}
|
|
490
|
+
if (tokens.length === 1 && tokens[0].value === "info") {
|
|
491
|
+
inInfoBlock = true;
|
|
492
|
+
infoIndent = indent;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (inInfoBlock && indent <= (infoIndent ?? 0)) {
|
|
496
|
+
inInfoBlock = false;
|
|
497
|
+
}
|
|
498
|
+
if (inInfoBlock) {
|
|
499
|
+
currentObject.infoEntries.push(parseInfoEntry(tokens, lineNumber));
|
|
500
|
+
} else {
|
|
501
|
+
currentObject.blockFields.push(parseField(tokens, lineNumber));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
type: "document",
|
|
506
|
+
objects
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function parseObjectHeader(tokens, line) {
|
|
510
|
+
if (tokens.length < 2) {
|
|
511
|
+
throw new WorldOrbitError("Invalid object declaration", line, tokens[0]?.column ?? 1);
|
|
512
|
+
}
|
|
513
|
+
const [objectTypeToken, nameToken, ...rest] = tokens;
|
|
514
|
+
if (!WORLDORBIT_OBJECT_TYPES.has(objectTypeToken.value)) {
|
|
515
|
+
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
type: "object",
|
|
519
|
+
objectType: objectTypeToken.value,
|
|
520
|
+
name: nameToken.value,
|
|
521
|
+
inlineFields: parseInlineFields(rest, line),
|
|
522
|
+
blockFields: [],
|
|
523
|
+
infoEntries: [],
|
|
524
|
+
location: { line, column: objectTypeToken.column }
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function parseInlineFields(tokens, line) {
|
|
528
|
+
const fields = [];
|
|
529
|
+
let index = 0;
|
|
530
|
+
while (index < tokens.length) {
|
|
531
|
+
const keyToken = tokens[index];
|
|
532
|
+
const schema = getFieldSchema(keyToken.value);
|
|
533
|
+
if (!schema) {
|
|
534
|
+
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
535
|
+
}
|
|
536
|
+
index++;
|
|
537
|
+
const valueTokens = [];
|
|
538
|
+
if (schema.arity === "multiple") {
|
|
539
|
+
while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
|
|
540
|
+
valueTokens.push(tokens[index]);
|
|
541
|
+
index++;
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
const nextToken = tokens[index];
|
|
545
|
+
if (nextToken) {
|
|
546
|
+
valueTokens.push(nextToken);
|
|
547
|
+
index++;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (valueTokens.length === 0) {
|
|
551
|
+
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
552
|
+
}
|
|
553
|
+
fields.push({
|
|
554
|
+
type: "field",
|
|
555
|
+
key: keyToken.value,
|
|
556
|
+
values: valueTokens.map((token) => token.value),
|
|
557
|
+
location: { line, column: keyToken.column }
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return fields;
|
|
561
|
+
}
|
|
562
|
+
function parseField(tokens, line) {
|
|
563
|
+
if (tokens.length < 2) {
|
|
564
|
+
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
565
|
+
}
|
|
566
|
+
if (!getFieldSchema(tokens[0].value)) {
|
|
567
|
+
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
type: "field",
|
|
571
|
+
key: tokens[0].value,
|
|
572
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
573
|
+
location: { line, column: tokens[0].column }
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function parseInfoEntry(tokens, line) {
|
|
577
|
+
if (tokens.length < 2) {
|
|
578
|
+
throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
type: "info-entry",
|
|
582
|
+
key: tokens[0].value,
|
|
583
|
+
value: tokens.slice(1).map((token) => token.value).join(" "),
|
|
584
|
+
location: { line, column: tokens[0].column }
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// packages/core/dist/normalize.js
|
|
589
|
+
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(au|km|re|sol|me|d|y|h|deg)?$/;
|
|
590
|
+
var BOOLEAN_VALUES = /* @__PURE__ */ new Map([
|
|
591
|
+
["true", true],
|
|
592
|
+
["false", false],
|
|
593
|
+
["yes", true],
|
|
594
|
+
["no", false]
|
|
595
|
+
]);
|
|
596
|
+
var URL_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
597
|
+
function normalizeDocument(ast) {
|
|
598
|
+
let system = null;
|
|
599
|
+
const objects = [];
|
|
600
|
+
for (const node of ast.objects) {
|
|
601
|
+
const normalized = normalizeObject(node);
|
|
602
|
+
if (node.objectType === "system") {
|
|
603
|
+
if (system) {
|
|
604
|
+
throw WorldOrbitError.fromLocation("Only one system object is allowed", node.location);
|
|
605
|
+
}
|
|
606
|
+
system = normalized;
|
|
607
|
+
} else {
|
|
608
|
+
objects.push(normalized);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
format: "worldorbit",
|
|
613
|
+
version: "1.0",
|
|
614
|
+
system,
|
|
615
|
+
objects
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function normalizeObject(node) {
|
|
619
|
+
const mergedFields = [...node.inlineFields, ...node.blockFields];
|
|
620
|
+
validateFieldCompatibility(node.objectType, mergedFields);
|
|
621
|
+
const fieldMap = collectFields(mergedFields);
|
|
622
|
+
const placement = extractPlacement(node.objectType, fieldMap);
|
|
623
|
+
const properties = normalizeProperties(fieldMap);
|
|
624
|
+
const info = normalizeInfo(node.infoEntries);
|
|
625
|
+
if (node.objectType === "system") {
|
|
626
|
+
return {
|
|
627
|
+
type: "system",
|
|
628
|
+
id: node.name,
|
|
629
|
+
properties,
|
|
630
|
+
info
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
type: node.objectType,
|
|
635
|
+
id: node.name,
|
|
636
|
+
properties,
|
|
637
|
+
placement,
|
|
638
|
+
info
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function validateFieldCompatibility(objectType, fields) {
|
|
642
|
+
for (const field of fields) {
|
|
643
|
+
const schema = getFieldSchema(field.key);
|
|
644
|
+
if (!schema) {
|
|
645
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
646
|
+
}
|
|
647
|
+
if (!supportsObjectType(schema, objectType)) {
|
|
648
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" is not valid on "${objectType}"`, field.location);
|
|
649
|
+
}
|
|
650
|
+
if (schema.arity === "single" && field.values.length !== 1) {
|
|
651
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function collectFields(fields) {
|
|
656
|
+
const map = /* @__PURE__ */ new Map();
|
|
657
|
+
for (const field of fields) {
|
|
658
|
+
if (map.has(field.key)) {
|
|
659
|
+
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
660
|
+
}
|
|
661
|
+
map.set(field.key, field);
|
|
662
|
+
}
|
|
663
|
+
return map;
|
|
664
|
+
}
|
|
665
|
+
function extractPlacement(objectType, fieldMap) {
|
|
666
|
+
const hasOrbit = fieldMap.has("orbit");
|
|
667
|
+
const hasAt = fieldMap.has("at");
|
|
668
|
+
const hasSurface = fieldMap.has("surface");
|
|
669
|
+
const hasFree = fieldMap.has("free");
|
|
670
|
+
const count = [hasOrbit, hasAt, hasSurface, hasFree].filter(Boolean).length;
|
|
671
|
+
if (count > 1) {
|
|
672
|
+
const conflictingField = fieldMap.get("orbit") ?? fieldMap.get("at") ?? fieldMap.get("surface") ?? fieldMap.get("free");
|
|
673
|
+
throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
|
|
674
|
+
}
|
|
675
|
+
if (objectType === "system" && count > 0) {
|
|
676
|
+
throw WorldOrbitError.fromLocation("System objects cannot declare placement", [...fieldMap.values()][0]?.location);
|
|
677
|
+
}
|
|
678
|
+
if (hasOrbit) {
|
|
679
|
+
return {
|
|
680
|
+
mode: "orbit",
|
|
681
|
+
target: singleValue(fieldMap, "orbit"),
|
|
682
|
+
distance: parseOptionalUnitValue(fieldMap, "distance"),
|
|
683
|
+
semiMajor: parseOptionalUnitValue(fieldMap, "semiMajor"),
|
|
684
|
+
eccentricity: parseOptionalNumber(fieldMap, "eccentricity"),
|
|
685
|
+
period: parseOptionalUnitValue(fieldMap, "period"),
|
|
686
|
+
angle: parseOptionalUnitValue(fieldMap, "angle"),
|
|
687
|
+
inclination: parseOptionalUnitValue(fieldMap, "inclination"),
|
|
688
|
+
phase: parseOptionalUnitValue(fieldMap, "phase")
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (hasAt) {
|
|
692
|
+
const field = getField(fieldMap, "at");
|
|
693
|
+
const target = singleValue(fieldMap, "at");
|
|
694
|
+
return {
|
|
695
|
+
mode: "at",
|
|
696
|
+
target,
|
|
697
|
+
reference: parseAtReference(target, field.location)
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (hasSurface) {
|
|
701
|
+
return {
|
|
702
|
+
mode: "surface",
|
|
703
|
+
target: singleValue(fieldMap, "surface")
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
if (hasFree) {
|
|
707
|
+
const raw = singleValue(fieldMap, "free");
|
|
708
|
+
const distance = tryParseUnitValue(raw);
|
|
709
|
+
return {
|
|
710
|
+
mode: "free",
|
|
711
|
+
distance: distance ?? void 0,
|
|
712
|
+
descriptor: distance ? void 0 : raw
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
function normalizeProperties(fieldMap) {
|
|
718
|
+
const result = {};
|
|
719
|
+
for (const [key, field] of fieldMap.entries()) {
|
|
720
|
+
const schema = getFieldSchema(key);
|
|
721
|
+
if (!schema || schema.placement) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
switch (schema.kind) {
|
|
725
|
+
case "list":
|
|
726
|
+
result[key] = field.values;
|
|
727
|
+
break;
|
|
728
|
+
case "boolean":
|
|
729
|
+
result[key] = parseBoolean(field);
|
|
730
|
+
break;
|
|
731
|
+
case "number":
|
|
732
|
+
result[key] = parseNumber(singleFieldValue(field), key, field.location);
|
|
733
|
+
break;
|
|
734
|
+
case "unit":
|
|
735
|
+
result[key] = parseUnitValue(singleFieldValue(field), field.location, key);
|
|
736
|
+
break;
|
|
737
|
+
case "string":
|
|
738
|
+
result[key] = normalizeStringValue(key, field);
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
function normalizeStringValue(key, field) {
|
|
745
|
+
const value = field.values.join(" ").trim();
|
|
746
|
+
if (key === "image") {
|
|
747
|
+
validateImageSource(value, field.location);
|
|
748
|
+
}
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
function validateImageSource(value, location) {
|
|
752
|
+
if (!value) {
|
|
753
|
+
throw WorldOrbitError.fromLocation('Field "image" must not be empty', location);
|
|
754
|
+
}
|
|
755
|
+
if (value.startsWith("//")) {
|
|
756
|
+
throw WorldOrbitError.fromLocation('Field "image" must use a relative path, root-relative path, or an http/https URL', location);
|
|
757
|
+
}
|
|
758
|
+
const schemeMatch = value.match(URL_SCHEME_PATTERN);
|
|
759
|
+
if (!schemeMatch) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const scheme = schemeMatch[0].slice(0, -1).toLowerCase();
|
|
763
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
764
|
+
throw WorldOrbitError.fromLocation(`Field "image" does not support the "${scheme}" scheme`, location);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function normalizeInfo(entries) {
|
|
768
|
+
const info = {};
|
|
769
|
+
for (const entry of entries) {
|
|
770
|
+
if (entry.key in info) {
|
|
771
|
+
throw WorldOrbitError.fromLocation(`Duplicate info key "${entry.key}"`, entry.location);
|
|
772
|
+
}
|
|
773
|
+
info[entry.key] = entry.value;
|
|
774
|
+
}
|
|
775
|
+
return info;
|
|
776
|
+
}
|
|
777
|
+
function parseAtReference(target, location) {
|
|
778
|
+
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
779
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
780
|
+
}
|
|
781
|
+
const pairedMatch = target.match(/^([A-Za-z0-9._-]+)-([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
782
|
+
if (pairedMatch) {
|
|
783
|
+
return {
|
|
784
|
+
kind: "lagrange",
|
|
785
|
+
primary: pairedMatch[1],
|
|
786
|
+
secondary: pairedMatch[2],
|
|
787
|
+
point: pairedMatch[3]
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
const simpleMatch = target.match(/^([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
791
|
+
if (simpleMatch) {
|
|
792
|
+
return {
|
|
793
|
+
kind: "lagrange",
|
|
794
|
+
primary: simpleMatch[1],
|
|
795
|
+
secondary: null,
|
|
796
|
+
point: simpleMatch[2]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (/^[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
800
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
801
|
+
}
|
|
802
|
+
const anchorMatch = target.match(/^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+)$/);
|
|
803
|
+
if (anchorMatch) {
|
|
804
|
+
return {
|
|
805
|
+
kind: "anchor",
|
|
806
|
+
objectId: anchorMatch[1],
|
|
807
|
+
anchor: anchorMatch[2]
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
kind: "named",
|
|
812
|
+
name: target
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function parseUnitValue(input, location, fieldKey) {
|
|
816
|
+
const match = input.match(UNIT_PATTERN);
|
|
817
|
+
if (!match) {
|
|
818
|
+
throw WorldOrbitError.fromLocation(`Invalid unit value "${input}"`, location);
|
|
819
|
+
}
|
|
820
|
+
const unitValue = {
|
|
821
|
+
value: Number(match[1]),
|
|
822
|
+
unit: match[2] ?? null
|
|
823
|
+
};
|
|
824
|
+
if (fieldKey) {
|
|
825
|
+
const schema = getFieldSchema(fieldKey);
|
|
826
|
+
if (schema?.unitFamily && !unitFamilyAllowsUnit(schema.unitFamily, unitValue.unit)) {
|
|
827
|
+
throw WorldOrbitError.fromLocation(`Unit "${unitValue.unit ?? "none"}" is not valid for "${fieldKey}"`, location);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return unitValue;
|
|
831
|
+
}
|
|
832
|
+
function tryParseUnitValue(input) {
|
|
833
|
+
const match = input.match(UNIT_PATTERN);
|
|
834
|
+
if (!match) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
value: Number(match[1]),
|
|
839
|
+
unit: match[2] ?? null
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function parseOptionalUnitValue(fieldMap, key) {
|
|
843
|
+
if (!fieldMap.has(key)) {
|
|
844
|
+
return void 0;
|
|
845
|
+
}
|
|
846
|
+
const field = getField(fieldMap, key);
|
|
847
|
+
return parseUnitValue(singleFieldValue(field), field.location, key);
|
|
848
|
+
}
|
|
849
|
+
function parseOptionalNumber(fieldMap, key) {
|
|
850
|
+
if (!fieldMap.has(key)) {
|
|
851
|
+
return void 0;
|
|
852
|
+
}
|
|
853
|
+
const field = getField(fieldMap, key);
|
|
854
|
+
return parseNumber(singleFieldValue(field), key, field.location);
|
|
855
|
+
}
|
|
856
|
+
function parseNumber(input, key, location) {
|
|
857
|
+
const value = Number(input);
|
|
858
|
+
if (!Number.isFinite(value)) {
|
|
859
|
+
throw WorldOrbitError.fromLocation(`Invalid numeric value "${input}" for "${key}"`, location);
|
|
860
|
+
}
|
|
861
|
+
return value;
|
|
862
|
+
}
|
|
863
|
+
function parseBoolean(field) {
|
|
864
|
+
const rawValue = singleFieldValue(field).toLowerCase();
|
|
865
|
+
const parsed = BOOLEAN_VALUES.get(rawValue);
|
|
866
|
+
if (parsed === void 0) {
|
|
867
|
+
throw WorldOrbitError.fromLocation(`Invalid boolean value "${rawValue}" for "${field.key}"`, field.location);
|
|
868
|
+
}
|
|
869
|
+
return parsed;
|
|
870
|
+
}
|
|
871
|
+
function getField(fieldMap, key) {
|
|
872
|
+
const field = fieldMap.get(key);
|
|
873
|
+
if (!field) {
|
|
874
|
+
throw new WorldOrbitError(`Missing value for key "${key}"`);
|
|
875
|
+
}
|
|
876
|
+
return field;
|
|
877
|
+
}
|
|
878
|
+
function singleValue(fieldMap, key) {
|
|
879
|
+
return singleFieldValue(getField(fieldMap, key));
|
|
880
|
+
}
|
|
881
|
+
function singleFieldValue(field) {
|
|
882
|
+
if (field.values.length !== 1) {
|
|
883
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
884
|
+
}
|
|
885
|
+
return field.values[0];
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// packages/core/dist/validate.js
|
|
889
|
+
var SURFACE_TARGET_TYPES = /* @__PURE__ */ new Set([
|
|
890
|
+
"star",
|
|
891
|
+
"planet",
|
|
892
|
+
"moon",
|
|
893
|
+
"asteroid",
|
|
894
|
+
"comet"
|
|
895
|
+
]);
|
|
896
|
+
function validateDocument(doc) {
|
|
897
|
+
const knownIds = /* @__PURE__ */ new Set();
|
|
898
|
+
const objectMap = /* @__PURE__ */ new Map();
|
|
899
|
+
for (const obj of doc.objects) {
|
|
900
|
+
if (knownIds.has(obj.id)) {
|
|
901
|
+
throw new WorldOrbitError(`Duplicate object id "${obj.id}"`);
|
|
902
|
+
}
|
|
903
|
+
knownIds.add(obj.id);
|
|
904
|
+
objectMap.set(obj.id, obj);
|
|
905
|
+
}
|
|
906
|
+
for (const obj of doc.objects) {
|
|
907
|
+
if (!obj.placement) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (obj.placement.mode === "orbit" || obj.placement.mode === "surface") {
|
|
911
|
+
if (!knownIds.has(obj.placement.target)) {
|
|
912
|
+
throw new WorldOrbitError(`Unknown placement target "${obj.placement.target}" on "${obj.id}"`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (obj.placement.mode === "surface") {
|
|
916
|
+
const target = objectMap.get(obj.placement.target);
|
|
917
|
+
if (!target || !SURFACE_TARGET_TYPES.has(target.type)) {
|
|
918
|
+
throw new WorldOrbitError(`Surface target "${obj.placement.target}" on "${obj.id}" is not surface-capable`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (obj.placement.mode === "at") {
|
|
922
|
+
if (obj.placement.reference.kind === "lagrange") {
|
|
923
|
+
validateLagrangeReference(obj, obj.placement.reference, knownIds);
|
|
924
|
+
}
|
|
925
|
+
if (obj.placement.reference.kind === "anchor") {
|
|
926
|
+
validateAnchorReference(obj, obj.placement.reference, knownIds);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function validateLagrangeReference(obj, reference, knownIds) {
|
|
932
|
+
if (!knownIds.has(reference.primary)) {
|
|
933
|
+
throw new WorldOrbitError(`Unknown Lagrange reference "${reference.primary}" on "${obj.id}"`);
|
|
934
|
+
}
|
|
935
|
+
if (reference.secondary && !knownIds.has(reference.secondary)) {
|
|
936
|
+
throw new WorldOrbitError(`Unknown Lagrange reference "${reference.secondary}" on "${obj.id}"`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function validateAnchorReference(obj, reference, knownIds) {
|
|
940
|
+
if (!knownIds.has(reference.objectId)) {
|
|
941
|
+
throw new WorldOrbitError(`Unknown anchor target "${reference.objectId}" on "${obj.id}"`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// packages/core/dist/diagnostics.js
|
|
946
|
+
function createDiagnostic(diagnostic) {
|
|
947
|
+
return { ...diagnostic };
|
|
948
|
+
}
|
|
949
|
+
function diagnosticFromError(error, source, code = `${source}.failed`) {
|
|
950
|
+
if (error instanceof WorldOrbitError) {
|
|
951
|
+
return {
|
|
952
|
+
code,
|
|
953
|
+
severity: "error",
|
|
954
|
+
source,
|
|
955
|
+
message: error.message,
|
|
956
|
+
line: error.line,
|
|
957
|
+
column: error.column
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
if (error instanceof Error) {
|
|
961
|
+
return {
|
|
962
|
+
code,
|
|
963
|
+
severity: "error",
|
|
964
|
+
source,
|
|
965
|
+
message: error.message
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
code,
|
|
970
|
+
severity: "error",
|
|
971
|
+
source,
|
|
972
|
+
message: String(error)
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function parseWithDiagnostics(source) {
|
|
976
|
+
let ast;
|
|
977
|
+
try {
|
|
978
|
+
ast = parseWorldOrbit(source);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
const diagnostic = diagnosticFromError(error, "parse");
|
|
981
|
+
return {
|
|
982
|
+
ok: false,
|
|
983
|
+
value: null,
|
|
984
|
+
diagnostics: [diagnostic]
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
let document;
|
|
988
|
+
try {
|
|
989
|
+
document = normalizeDocument(ast);
|
|
990
|
+
} catch (error) {
|
|
991
|
+
return {
|
|
992
|
+
ok: false,
|
|
993
|
+
value: null,
|
|
994
|
+
diagnostics: [diagnosticFromError(error, "normalize")]
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
try {
|
|
998
|
+
validateDocument(document);
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
return {
|
|
1001
|
+
ok: false,
|
|
1002
|
+
value: null,
|
|
1003
|
+
diagnostics: [diagnosticFromError(error, "validate")]
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
ok: true,
|
|
1008
|
+
value: {
|
|
1009
|
+
ast,
|
|
1010
|
+
document
|
|
1011
|
+
},
|
|
1012
|
+
diagnostics: []
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function normalizeWithDiagnostics(ast) {
|
|
1016
|
+
try {
|
|
1017
|
+
return {
|
|
1018
|
+
ok: true,
|
|
1019
|
+
value: normalizeDocument(ast),
|
|
1020
|
+
diagnostics: []
|
|
1021
|
+
};
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
return {
|
|
1024
|
+
ok: false,
|
|
1025
|
+
value: null,
|
|
1026
|
+
diagnostics: [diagnosticFromError(error, "normalize")]
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function validateDocumentWithDiagnostics(document) {
|
|
1031
|
+
try {
|
|
1032
|
+
validateDocument(document);
|
|
1033
|
+
return {
|
|
1034
|
+
ok: true,
|
|
1035
|
+
value: document,
|
|
1036
|
+
diagnostics: []
|
|
1037
|
+
};
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
return {
|
|
1040
|
+
ok: false,
|
|
1041
|
+
value: null,
|
|
1042
|
+
diagnostics: [diagnosticFromError(error, "validate")]
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// packages/core/dist/scene.js
|
|
1048
|
+
var AU_IN_KM = 1495978707e-1;
|
|
1049
|
+
var EARTH_RADIUS_IN_KM = 6371;
|
|
1050
|
+
var SOLAR_RADIUS_IN_KM = 695700;
|
|
1051
|
+
var ISO_FLATTENING = 0.68;
|
|
1052
|
+
var MIN_ISO_MINOR_SCALE = 0.2;
|
|
1053
|
+
var ARC_SAMPLE_COUNT = 28;
|
|
1054
|
+
function renderDocumentToScene(document, options = {}) {
|
|
1055
|
+
const frame = resolveSceneFrame(options);
|
|
1056
|
+
const width = frame.width;
|
|
1057
|
+
const height = frame.height;
|
|
1058
|
+
const padding = frame.padding;
|
|
1059
|
+
const layoutPreset = resolveLayoutPreset(document);
|
|
1060
|
+
const projection = resolveProjection(document, options.projection);
|
|
1061
|
+
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
1062
|
+
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
1063
|
+
const systemId = document.system?.id ?? null;
|
|
1064
|
+
const objectMap = new Map(document.objects.map((object) => [object.id, object]));
|
|
1065
|
+
const relationships = buildSceneRelationships(document.objects, objectMap);
|
|
1066
|
+
const positions = /* @__PURE__ */ new Map();
|
|
1067
|
+
const orbitDrafts = [];
|
|
1068
|
+
const leaderDrafts = [];
|
|
1069
|
+
const rootObjects = [];
|
|
1070
|
+
const freeObjects = [];
|
|
1071
|
+
const atObjects = [];
|
|
1072
|
+
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
1073
|
+
const orbitChildren = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (const object of document.objects) {
|
|
1075
|
+
const placement = object.placement;
|
|
1076
|
+
if (!placement) {
|
|
1077
|
+
rootObjects.push(object);
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (placement.mode === "orbit") {
|
|
1081
|
+
pushGrouped(orbitChildren, placement.target, object);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (placement.mode === "surface") {
|
|
1085
|
+
pushGrouped(surfaceChildren, placement.target, object);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (placement.mode === "at") {
|
|
1089
|
+
atObjects.push(object);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
freeObjects.push(object);
|
|
1093
|
+
}
|
|
1094
|
+
const centerX = freeObjects.length > 0 ? width * 0.42 : width / 2;
|
|
1095
|
+
const centerY = height / 2;
|
|
1096
|
+
const context = {
|
|
1097
|
+
orbitChildren,
|
|
1098
|
+
surfaceChildren,
|
|
1099
|
+
objectMap,
|
|
1100
|
+
spacingFactor,
|
|
1101
|
+
projection,
|
|
1102
|
+
scaleModel
|
|
1103
|
+
};
|
|
1104
|
+
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
1105
|
+
if (primaryRoot) {
|
|
1106
|
+
placeObject(primaryRoot, centerX, centerY, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
1107
|
+
}
|
|
1108
|
+
const secondaryRoots = rootObjects.filter((object) => object.id !== primaryRoot?.id);
|
|
1109
|
+
if (secondaryRoots.length > 0) {
|
|
1110
|
+
const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
1111
|
+
secondaryRoots.forEach((object, index) => {
|
|
1112
|
+
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
1113
|
+
const offset = projectPolarOffset(angle, rootRingRadius, projection, 1);
|
|
1114
|
+
placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
freeObjects.forEach((object, index) => {
|
|
1118
|
+
const x = width - padding - 140 - freePlacementOffsetPx(object.placement?.mode === "free" ? object.placement.distance : void 0, scaleModel);
|
|
1119
|
+
const rowStep = Math.max(76, (height - padding * 2 - 180) / Math.max(1, freeObjects.length) * spacingFactor) * scaleModel.freePlacementMultiplier;
|
|
1120
|
+
const y = padding + 92 + index * rowStep;
|
|
1121
|
+
positions.set(object.id, {
|
|
1122
|
+
object,
|
|
1123
|
+
x,
|
|
1124
|
+
y,
|
|
1125
|
+
radius: visualRadiusFor(object, 0, scaleModel),
|
|
1126
|
+
sortKey: computeSortKey(x, y, 0)
|
|
1127
|
+
});
|
|
1128
|
+
leaderDrafts.push({
|
|
1129
|
+
object,
|
|
1130
|
+
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1131
|
+
x1: x - 60,
|
|
1132
|
+
y1: y,
|
|
1133
|
+
x2: x - 18,
|
|
1134
|
+
y2: y,
|
|
1135
|
+
mode: "free"
|
|
1136
|
+
});
|
|
1137
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, 1);
|
|
1138
|
+
});
|
|
1139
|
+
atObjects.forEach((object, index) => {
|
|
1140
|
+
if (positions.has(object.id) || !object.placement || object.placement.mode !== "at") {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
const resolved = resolveAtPosition(object.placement.reference, positions, objectMap, index, atObjects.length, width, height, padding, context);
|
|
1144
|
+
positions.set(object.id, {
|
|
1145
|
+
object,
|
|
1146
|
+
x: resolved.x,
|
|
1147
|
+
y: resolved.y,
|
|
1148
|
+
radius: visualRadiusFor(object, 2, scaleModel),
|
|
1149
|
+
sortKey: computeSortKey(resolved.x, resolved.y, 2),
|
|
1150
|
+
anchorX: resolved.anchorX,
|
|
1151
|
+
anchorY: resolved.anchorY
|
|
1152
|
+
});
|
|
1153
|
+
if (resolved.anchorX !== void 0 && resolved.anchorY !== void 0) {
|
|
1154
|
+
leaderDrafts.push({
|
|
1155
|
+
object,
|
|
1156
|
+
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1157
|
+
x1: resolved.anchorX,
|
|
1158
|
+
y1: resolved.anchorY,
|
|
1159
|
+
x2: resolved.x,
|
|
1160
|
+
y2: resolved.y,
|
|
1161
|
+
mode: "at"
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, 2);
|
|
1165
|
+
});
|
|
1166
|
+
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1167
|
+
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1168
|
+
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1169
|
+
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1170
|
+
const layers = createSceneLayers(orbitVisuals, leaders, objects, labels);
|
|
1171
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
|
|
1172
|
+
const viewpoints = createSceneViewpoints(document, projection, frame.preset, relationships, objectMap);
|
|
1173
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1174
|
+
return {
|
|
1175
|
+
width,
|
|
1176
|
+
height,
|
|
1177
|
+
padding,
|
|
1178
|
+
renderPreset: frame.preset,
|
|
1179
|
+
projection,
|
|
1180
|
+
scaleModel,
|
|
1181
|
+
title: String(document.system?.properties.title ?? document.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1182
|
+
subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
|
|
1183
|
+
systemId,
|
|
1184
|
+
viewMode: projection,
|
|
1185
|
+
layoutPreset,
|
|
1186
|
+
metadata: {
|
|
1187
|
+
format: document.format,
|
|
1188
|
+
version: document.version,
|
|
1189
|
+
view: projection,
|
|
1190
|
+
scale: String(document.system?.properties.scale ?? layoutPreset),
|
|
1191
|
+
units: String(document.system?.properties.units ?? "mixed"),
|
|
1192
|
+
preset: frame.preset ?? "custom"
|
|
1193
|
+
},
|
|
1194
|
+
contentBounds,
|
|
1195
|
+
layers,
|
|
1196
|
+
groups,
|
|
1197
|
+
viewpoints,
|
|
1198
|
+
objects,
|
|
1199
|
+
orbitVisuals,
|
|
1200
|
+
leaders,
|
|
1201
|
+
labels
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
function rotatePoint(point, center, rotationDeg) {
|
|
1205
|
+
const radians = degreesToRadians(rotationDeg);
|
|
1206
|
+
const cos = Math.cos(radians);
|
|
1207
|
+
const sin = Math.sin(radians);
|
|
1208
|
+
const dx = point.x - center.x;
|
|
1209
|
+
const dy = point.y - center.y;
|
|
1210
|
+
return {
|
|
1211
|
+
x: center.x + dx * cos - dy * sin,
|
|
1212
|
+
y: center.y + dx * sin + dy * cos
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function resolveLayoutPreset(document) {
|
|
1216
|
+
const rawScale = String(document.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1217
|
+
switch (rawScale) {
|
|
1218
|
+
case "compressed":
|
|
1219
|
+
case "compact":
|
|
1220
|
+
return "compact";
|
|
1221
|
+
case "expanded":
|
|
1222
|
+
case "presentation":
|
|
1223
|
+
return "presentation";
|
|
1224
|
+
default:
|
|
1225
|
+
return "balanced";
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function resolveSceneFrame(options) {
|
|
1229
|
+
const defaults = scenePresetDefaults(options.preset);
|
|
1230
|
+
return {
|
|
1231
|
+
width: options.width ?? defaults.width,
|
|
1232
|
+
height: options.height ?? defaults.height,
|
|
1233
|
+
padding: options.padding ?? defaults.padding,
|
|
1234
|
+
preset: options.preset ?? null
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function scenePresetDefaults(preset) {
|
|
1238
|
+
switch (preset) {
|
|
1239
|
+
case "presentation":
|
|
1240
|
+
return { width: 1440, height: 900, padding: 88 };
|
|
1241
|
+
case "atlas-card":
|
|
1242
|
+
return { width: 960, height: 560, padding: 56 };
|
|
1243
|
+
case "markdown":
|
|
1244
|
+
return { width: 920, height: 540, padding: 48 };
|
|
1245
|
+
case "diagram":
|
|
1246
|
+
default:
|
|
1247
|
+
return { width: 1200, height: 780, padding: 72 };
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function resolveProjection(document, projection) {
|
|
1251
|
+
if (projection === "topdown" || projection === "isometric") {
|
|
1252
|
+
return projection;
|
|
1253
|
+
}
|
|
1254
|
+
return String(document.system?.properties.view ?? "topdown").toLowerCase() === "isometric" ? "isometric" : "topdown";
|
|
1255
|
+
}
|
|
1256
|
+
function resolveScaleModel(layoutPreset, overrides) {
|
|
1257
|
+
const defaults = defaultScaleModel(layoutPreset);
|
|
1258
|
+
return {
|
|
1259
|
+
...defaults,
|
|
1260
|
+
...overrides
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function defaultScaleModel(layoutPreset) {
|
|
1264
|
+
switch (layoutPreset) {
|
|
1265
|
+
case "compact":
|
|
1266
|
+
return {
|
|
1267
|
+
orbitDistanceMultiplier: 0.84,
|
|
1268
|
+
bodyRadiusMultiplier: 0.92,
|
|
1269
|
+
labelMultiplier: 0.9,
|
|
1270
|
+
freePlacementMultiplier: 0.9,
|
|
1271
|
+
ringThicknessMultiplier: 0.92,
|
|
1272
|
+
minBodyRadius: 4,
|
|
1273
|
+
maxBodyRadius: 36
|
|
1274
|
+
};
|
|
1275
|
+
case "presentation":
|
|
1276
|
+
return {
|
|
1277
|
+
orbitDistanceMultiplier: 1.2,
|
|
1278
|
+
bodyRadiusMultiplier: 1.18,
|
|
1279
|
+
labelMultiplier: 1.08,
|
|
1280
|
+
freePlacementMultiplier: 1.05,
|
|
1281
|
+
ringThicknessMultiplier: 1.16,
|
|
1282
|
+
minBodyRadius: 5,
|
|
1283
|
+
maxBodyRadius: 48
|
|
1284
|
+
};
|
|
1285
|
+
default:
|
|
1286
|
+
return {
|
|
1287
|
+
orbitDistanceMultiplier: 1,
|
|
1288
|
+
bodyRadiusMultiplier: 1,
|
|
1289
|
+
labelMultiplier: 1,
|
|
1290
|
+
freePlacementMultiplier: 1,
|
|
1291
|
+
ringThicknessMultiplier: 1,
|
|
1292
|
+
minBodyRadius: 4,
|
|
1293
|
+
maxBodyRadius: 40
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function layoutPresetSpacing(layoutPreset) {
|
|
1298
|
+
switch (layoutPreset) {
|
|
1299
|
+
case "compact":
|
|
1300
|
+
return 0.84;
|
|
1301
|
+
case "presentation":
|
|
1302
|
+
return 1.2;
|
|
1303
|
+
default:
|
|
1304
|
+
return 1;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function createSceneObject(position, scaleModel, relationships) {
|
|
1308
|
+
const { object, x, y, radius, sortKey, anchorX, anchorY } = position;
|
|
1309
|
+
return {
|
|
1310
|
+
renderId: createRenderId(object.id),
|
|
1311
|
+
objectId: object.id,
|
|
1312
|
+
object,
|
|
1313
|
+
parentId: relationships.parentIds.get(object.id) ?? null,
|
|
1314
|
+
ancestorIds: relationships.ancestorIds.get(object.id) ?? [],
|
|
1315
|
+
childIds: relationships.childIds.get(object.id) ?? [],
|
|
1316
|
+
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1317
|
+
x,
|
|
1318
|
+
y,
|
|
1319
|
+
radius,
|
|
1320
|
+
visualRadius: visualExtentForObject(object, radius, scaleModel),
|
|
1321
|
+
sortKey,
|
|
1322
|
+
anchorX,
|
|
1323
|
+
anchorY,
|
|
1324
|
+
label: object.id,
|
|
1325
|
+
secondaryLabel: object.type === "structure" ? String(object.properties.kind ?? object.type) : object.type,
|
|
1326
|
+
fillColor: customColorFor(object.properties.color),
|
|
1327
|
+
imageHref: typeof object.properties.image === "string" && object.properties.image.trim() ? object.properties.image : void 0,
|
|
1328
|
+
hidden: object.properties.hidden === true
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function createOrbitVisual(draft, groupId) {
|
|
1332
|
+
return {
|
|
1333
|
+
renderId: `${createRenderId(draft.object.id)}-orbit`,
|
|
1334
|
+
objectId: draft.object.id,
|
|
1335
|
+
object: draft.object,
|
|
1336
|
+
parentId: draft.parentId,
|
|
1337
|
+
groupId,
|
|
1338
|
+
kind: draft.kind,
|
|
1339
|
+
cx: draft.cx,
|
|
1340
|
+
cy: draft.cy,
|
|
1341
|
+
radius: draft.radius,
|
|
1342
|
+
rx: draft.rx,
|
|
1343
|
+
ry: draft.ry,
|
|
1344
|
+
rotationDeg: draft.rotationDeg,
|
|
1345
|
+
band: draft.band,
|
|
1346
|
+
bandThickness: draft.bandThickness,
|
|
1347
|
+
frontArcPath: draft.frontArcPath,
|
|
1348
|
+
backArcPath: draft.backArcPath,
|
|
1349
|
+
hidden: draft.object.properties.hidden === true
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function createLeaderLine(draft) {
|
|
1353
|
+
return {
|
|
1354
|
+
renderId: `${createRenderId(draft.object.id)}-leader-${draft.mode}`,
|
|
1355
|
+
objectId: draft.object.id,
|
|
1356
|
+
object: draft.object,
|
|
1357
|
+
groupId: draft.groupId,
|
|
1358
|
+
x1: draft.x1,
|
|
1359
|
+
y1: draft.y1,
|
|
1360
|
+
x2: draft.x2,
|
|
1361
|
+
y2: draft.y2,
|
|
1362
|
+
mode: draft.mode,
|
|
1363
|
+
hidden: draft.object.properties.hidden === true
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1367
|
+
const labels = [];
|
|
1368
|
+
const occupied = [];
|
|
1369
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden).sort((left, right) => left.sortKey - right.sortKey);
|
|
1370
|
+
for (const object of visibleObjects) {
|
|
1371
|
+
const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
|
|
1372
|
+
const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
|
|
1373
|
+
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1374
|
+
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1375
|
+
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1376
|
+
let attempts = 0;
|
|
1377
|
+
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1378
|
+
labelY += direction * 14 * labelMultiplier;
|
|
1379
|
+
secondaryY += direction * 14 * labelMultiplier;
|
|
1380
|
+
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1381
|
+
attempts += 1;
|
|
1382
|
+
}
|
|
1383
|
+
occupied.push(bounds);
|
|
1384
|
+
labels.push({
|
|
1385
|
+
renderId: `${object.renderId}-label`,
|
|
1386
|
+
objectId: object.objectId,
|
|
1387
|
+
object: object.object,
|
|
1388
|
+
groupId: object.groupId,
|
|
1389
|
+
label: object.label,
|
|
1390
|
+
secondaryLabel: object.secondaryLabel,
|
|
1391
|
+
x: object.x,
|
|
1392
|
+
y: labelY,
|
|
1393
|
+
secondaryY,
|
|
1394
|
+
textAnchor: "middle",
|
|
1395
|
+
direction: direction < 0 ? "above" : "below",
|
|
1396
|
+
hidden: object.hidden
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
return labels;
|
|
1400
|
+
}
|
|
1401
|
+
function createSceneLayers(orbitVisuals, leaders, objects, labels) {
|
|
1402
|
+
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1403
|
+
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1404
|
+
return [
|
|
1405
|
+
{ id: "background", renderIds: ["wo-bg", "wo-bg-glow", "wo-grid"] },
|
|
1406
|
+
{
|
|
1407
|
+
id: "guides",
|
|
1408
|
+
renderIds: leaders.filter((leader) => !leader.hidden).map((leader) => leader.renderId)
|
|
1409
|
+
},
|
|
1410
|
+
{ id: "orbits-back", renderIds: backOrbitIds },
|
|
1411
|
+
{ id: "orbits-front", renderIds: frontOrbitIds },
|
|
1412
|
+
{
|
|
1413
|
+
id: "objects",
|
|
1414
|
+
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
id: "labels",
|
|
1418
|
+
renderIds: labels.filter((label) => !label.hidden).map((label) => label.renderId)
|
|
1419
|
+
},
|
|
1420
|
+
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1421
|
+
];
|
|
1422
|
+
}
|
|
1423
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1424
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1425
|
+
const ensureGroup = (groupId) => {
|
|
1426
|
+
if (!groupId) {
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
const existing = groups.get(groupId);
|
|
1430
|
+
if (existing) {
|
|
1431
|
+
return existing;
|
|
1432
|
+
}
|
|
1433
|
+
const rootObjectId = relationships.groupRoots.get(groupId) ?? null;
|
|
1434
|
+
const created = {
|
|
1435
|
+
renderId: groupId,
|
|
1436
|
+
rootObjectId,
|
|
1437
|
+
label: rootObjectId ?? groupId,
|
|
1438
|
+
objectIds: [],
|
|
1439
|
+
orbitIds: [],
|
|
1440
|
+
labelIds: [],
|
|
1441
|
+
leaderIds: [],
|
|
1442
|
+
contentBounds: createBounds(0, 0, 0, 0)
|
|
1443
|
+
};
|
|
1444
|
+
groups.set(groupId, created);
|
|
1445
|
+
return created;
|
|
1446
|
+
};
|
|
1447
|
+
for (const object of objects) {
|
|
1448
|
+
const group = ensureGroup(object.groupId);
|
|
1449
|
+
if (group && !object.hidden) {
|
|
1450
|
+
group.objectIds.push(object.objectId);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
for (const orbit of orbitVisuals) {
|
|
1454
|
+
const group = ensureGroup(orbit.groupId);
|
|
1455
|
+
if (group && !orbit.hidden) {
|
|
1456
|
+
group.orbitIds.push(orbit.objectId);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
for (const leader of leaders) {
|
|
1460
|
+
const group = ensureGroup(leader.groupId);
|
|
1461
|
+
if (group && !leader.hidden) {
|
|
1462
|
+
group.leaderIds.push(leader.objectId);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
for (const label of labels) {
|
|
1466
|
+
const group = ensureGroup(label.groupId);
|
|
1467
|
+
if (group && !label.hidden) {
|
|
1468
|
+
group.labelIds.push(label.objectId);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
for (const group of groups.values()) {
|
|
1472
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1473
|
+
}
|
|
1474
|
+
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1475
|
+
}
|
|
1476
|
+
function createSceneViewpoints(document, projection, preset, relationships, objectMap) {
|
|
1477
|
+
const generatedOverview = createGeneratedOverviewViewpoint(document, projection, preset);
|
|
1478
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
1479
|
+
for (const [key, value] of Object.entries(document.system?.info ?? {})) {
|
|
1480
|
+
if (!key.startsWith("viewpoint.")) {
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
const [prefix, rawId, ...fieldParts] = key.split(".");
|
|
1484
|
+
if (prefix !== "viewpoint" || !rawId || fieldParts.length === 0) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const id = normalizeViewpointId(rawId);
|
|
1488
|
+
if (!id) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const field = fieldParts.join(".").toLowerCase();
|
|
1492
|
+
const draft = drafts.get(id) ?? { id };
|
|
1493
|
+
applyViewpointField(draft, field, value, projection, preset, relationships, objectMap);
|
|
1494
|
+
drafts.set(id, draft);
|
|
1495
|
+
}
|
|
1496
|
+
const viewpoints = [...drafts.values()].map((draft) => finalizeViewpointDraft(draft, projection, preset, objectMap)).filter(Boolean);
|
|
1497
|
+
const overviewIndex = viewpoints.findIndex((viewpoint) => viewpoint.id === generatedOverview.id);
|
|
1498
|
+
if (overviewIndex >= 0) {
|
|
1499
|
+
viewpoints.splice(overviewIndex, 1, {
|
|
1500
|
+
...generatedOverview,
|
|
1501
|
+
...viewpoints[overviewIndex],
|
|
1502
|
+
layers: {
|
|
1503
|
+
...generatedOverview.layers,
|
|
1504
|
+
...viewpoints[overviewIndex].layers
|
|
1505
|
+
},
|
|
1506
|
+
filter: viewpoints[overviewIndex].filter ?? generatedOverview.filter,
|
|
1507
|
+
generated: false
|
|
1508
|
+
});
|
|
1509
|
+
} else {
|
|
1510
|
+
viewpoints.unshift(generatedOverview);
|
|
1511
|
+
}
|
|
1512
|
+
return viewpoints.sort((left, right) => {
|
|
1513
|
+
if (left.id === "overview")
|
|
1514
|
+
return -1;
|
|
1515
|
+
if (right.id === "overview")
|
|
1516
|
+
return 1;
|
|
1517
|
+
return left.label.localeCompare(right.label);
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function createGeneratedOverviewViewpoint(document, projection, preset) {
|
|
1521
|
+
const label = document.system?.properties.title ? `${String(document.system.properties.title)} Overview` : "Overview";
|
|
1522
|
+
return {
|
|
1523
|
+
id: "overview",
|
|
1524
|
+
label,
|
|
1525
|
+
summary: "Fit the whole system with the current atlas defaults.",
|
|
1526
|
+
objectId: null,
|
|
1527
|
+
selectedObjectId: null,
|
|
1528
|
+
projection,
|
|
1529
|
+
preset,
|
|
1530
|
+
rotationDeg: 0,
|
|
1531
|
+
scale: null,
|
|
1532
|
+
layers: {},
|
|
1533
|
+
filter: null,
|
|
1534
|
+
generated: true
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
function applyViewpointField(draft, field, value, projection, preset, relationships, objectMap) {
|
|
1538
|
+
const normalizedValue = value.trim();
|
|
1539
|
+
switch (field) {
|
|
1540
|
+
case "label":
|
|
1541
|
+
case "title":
|
|
1542
|
+
if (normalizedValue) {
|
|
1543
|
+
draft.label = normalizedValue;
|
|
1544
|
+
}
|
|
1545
|
+
return;
|
|
1546
|
+
case "summary":
|
|
1547
|
+
case "description":
|
|
1548
|
+
if (normalizedValue) {
|
|
1549
|
+
draft.summary = normalizedValue;
|
|
1550
|
+
}
|
|
1551
|
+
return;
|
|
1552
|
+
case "focus":
|
|
1553
|
+
case "object":
|
|
1554
|
+
if (normalizedValue) {
|
|
1555
|
+
draft.focus = normalizedValue;
|
|
1556
|
+
}
|
|
1557
|
+
return;
|
|
1558
|
+
case "select":
|
|
1559
|
+
case "selection":
|
|
1560
|
+
if (normalizedValue) {
|
|
1561
|
+
draft.select = normalizedValue;
|
|
1562
|
+
}
|
|
1563
|
+
return;
|
|
1564
|
+
case "projection":
|
|
1565
|
+
case "view":
|
|
1566
|
+
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
1567
|
+
return;
|
|
1568
|
+
case "preset":
|
|
1569
|
+
draft.preset = parseRenderPreset(normalizedValue) ?? preset;
|
|
1570
|
+
return;
|
|
1571
|
+
case "rotation":
|
|
1572
|
+
case "angle":
|
|
1573
|
+
draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
|
|
1574
|
+
return;
|
|
1575
|
+
case "zoom":
|
|
1576
|
+
case "scale":
|
|
1577
|
+
draft.scale = parsePositiveNumber(normalizedValue);
|
|
1578
|
+
return;
|
|
1579
|
+
case "layers":
|
|
1580
|
+
draft.layers = parseViewpointLayers(normalizedValue);
|
|
1581
|
+
return;
|
|
1582
|
+
case "query":
|
|
1583
|
+
draft.filter = {
|
|
1584
|
+
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1585
|
+
query: normalizedValue || null
|
|
1586
|
+
};
|
|
1587
|
+
return;
|
|
1588
|
+
case "types":
|
|
1589
|
+
case "objecttypes":
|
|
1590
|
+
draft.filter = {
|
|
1591
|
+
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1592
|
+
objectTypes: parseViewpointObjectTypes(normalizedValue)
|
|
1593
|
+
};
|
|
1594
|
+
return;
|
|
1595
|
+
case "tags":
|
|
1596
|
+
draft.filter = {
|
|
1597
|
+
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1598
|
+
tags: splitListValue(normalizedValue)
|
|
1599
|
+
};
|
|
1600
|
+
return;
|
|
1601
|
+
case "groups":
|
|
1602
|
+
draft.filter = {
|
|
1603
|
+
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1604
|
+
groupIds: parseViewpointGroups(normalizedValue, relationships, objectMap)
|
|
1605
|
+
};
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function finalizeViewpointDraft(draft, projection, preset, objectMap) {
|
|
1610
|
+
const objectId = draft.focus && objectMap.has(draft.focus) ? draft.focus : null;
|
|
1611
|
+
const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
|
|
1612
|
+
const filter = normalizeViewpointFilter(draft.filter);
|
|
1613
|
+
const label = draft.label?.trim() || humanizeIdentifier(draft.id);
|
|
1614
|
+
return {
|
|
1615
|
+
id: draft.id,
|
|
1616
|
+
label,
|
|
1617
|
+
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1618
|
+
objectId,
|
|
1619
|
+
selectedObjectId,
|
|
1620
|
+
projection: draft.projection ?? projection,
|
|
1621
|
+
preset: draft.preset ?? preset,
|
|
1622
|
+
rotationDeg: draft.rotationDeg ?? 0,
|
|
1623
|
+
scale: draft.scale ?? null,
|
|
1624
|
+
layers: draft.layers ?? {},
|
|
1625
|
+
filter,
|
|
1626
|
+
generated: false
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function createEmptyViewpointFilter() {
|
|
1630
|
+
return {
|
|
1631
|
+
query: null,
|
|
1632
|
+
objectTypes: [],
|
|
1633
|
+
tags: [],
|
|
1634
|
+
groupIds: []
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function normalizeViewpointFilter(filter) {
|
|
1638
|
+
if (!filter) {
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1641
|
+
const normalized = {
|
|
1642
|
+
query: filter.query?.trim() || null,
|
|
1643
|
+
objectTypes: [...new Set(filter.objectTypes)],
|
|
1644
|
+
tags: [...new Set(filter.tags)],
|
|
1645
|
+
groupIds: [...new Set(filter.groupIds)]
|
|
1646
|
+
};
|
|
1647
|
+
return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
|
|
1648
|
+
}
|
|
1649
|
+
function parseViewProjection(value) {
|
|
1650
|
+
return value.toLowerCase() === "isometric" ? "isometric" : value.toLowerCase() === "topdown" ? "topdown" : null;
|
|
1651
|
+
}
|
|
1652
|
+
function parseRenderPreset(value) {
|
|
1653
|
+
const normalized = value.toLowerCase();
|
|
1654
|
+
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
1655
|
+
return normalized;
|
|
1656
|
+
}
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
function parseFiniteNumber(value) {
|
|
1660
|
+
const parsed = Number(value);
|
|
1661
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1662
|
+
}
|
|
1663
|
+
function parsePositiveNumber(value) {
|
|
1664
|
+
const parsed = parseFiniteNumber(value);
|
|
1665
|
+
return parsed !== null && parsed > 0 ? parsed : null;
|
|
1666
|
+
}
|
|
1667
|
+
function parseViewpointLayers(value) {
|
|
1668
|
+
const next = {};
|
|
1669
|
+
for (const token of splitListValue(value)) {
|
|
1670
|
+
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
1671
|
+
const rawLayer = token.replace(/^[-!]+/, "").toLowerCase();
|
|
1672
|
+
if (rawLayer === "orbits") {
|
|
1673
|
+
next["orbits-back"] = enabled;
|
|
1674
|
+
next["orbits-front"] = enabled;
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1678
|
+
next[rawLayer] = enabled;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return next;
|
|
1682
|
+
}
|
|
1683
|
+
function parseViewpointObjectTypes(value) {
|
|
1684
|
+
return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
|
|
1685
|
+
}
|
|
1686
|
+
function parseViewpointGroups(value, relationships, objectMap) {
|
|
1687
|
+
return splitListValue(value).map((entry) => {
|
|
1688
|
+
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
1689
|
+
return entry;
|
|
1690
|
+
}
|
|
1691
|
+
if (relationships.groupIds.has(entry)) {
|
|
1692
|
+
return relationships.groupIds.get(entry) ?? createGroupId(entry);
|
|
1693
|
+
}
|
|
1694
|
+
return objectMap.has(entry) ? createGroupId(entry) : createGroupId(entry);
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
function splitListValue(value) {
|
|
1698
|
+
return value.split(/[\s,]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
1699
|
+
}
|
|
1700
|
+
function normalizeViewpointId(value) {
|
|
1701
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1702
|
+
}
|
|
1703
|
+
function humanizeIdentifier(value) {
|
|
1704
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
1705
|
+
}
|
|
1706
|
+
function createViewpointSummary(label, objectId, filter) {
|
|
1707
|
+
const parts = [label];
|
|
1708
|
+
if (objectId) {
|
|
1709
|
+
parts.push(`focus ${objectId}`);
|
|
1710
|
+
}
|
|
1711
|
+
if (filter?.objectTypes.length) {
|
|
1712
|
+
parts.push(filter.objectTypes.join("/"));
|
|
1713
|
+
}
|
|
1714
|
+
if (filter?.tags.length) {
|
|
1715
|
+
parts.push(`tags ${filter.tags.join(", ")}`);
|
|
1716
|
+
}
|
|
1717
|
+
if (filter?.query) {
|
|
1718
|
+
parts.push(`query "${filter.query}"`);
|
|
1719
|
+
}
|
|
1720
|
+
return parts.join(" - ");
|
|
1721
|
+
}
|
|
1722
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
1723
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1724
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
1725
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1726
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
1727
|
+
const include = (x, y) => {
|
|
1728
|
+
minX = Math.min(minX, x);
|
|
1729
|
+
minY = Math.min(minY, y);
|
|
1730
|
+
maxX = Math.max(maxX, x);
|
|
1731
|
+
maxY = Math.max(maxY, y);
|
|
1732
|
+
};
|
|
1733
|
+
for (const orbit of orbitVisuals) {
|
|
1734
|
+
if (orbit.hidden)
|
|
1735
|
+
continue;
|
|
1736
|
+
includeOrbitBounds(orbit, include);
|
|
1737
|
+
}
|
|
1738
|
+
for (const leader of leaders) {
|
|
1739
|
+
if (leader.hidden)
|
|
1740
|
+
continue;
|
|
1741
|
+
include(leader.x1, leader.y1);
|
|
1742
|
+
include(leader.x2, leader.y2);
|
|
1743
|
+
}
|
|
1744
|
+
for (const object of objects) {
|
|
1745
|
+
if (object.hidden)
|
|
1746
|
+
continue;
|
|
1747
|
+
includeObjectBounds(object, include);
|
|
1748
|
+
}
|
|
1749
|
+
for (const label of labels) {
|
|
1750
|
+
if (label.hidden)
|
|
1751
|
+
continue;
|
|
1752
|
+
includeLabelBounds(label, include);
|
|
1753
|
+
}
|
|
1754
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1755
|
+
return createBounds(0, 0, width, height);
|
|
1756
|
+
}
|
|
1757
|
+
return createBounds(minX, minY, maxX, maxY);
|
|
1758
|
+
}
|
|
1759
|
+
function includeOrbitBounds(orbit, include) {
|
|
1760
|
+
const strokePadding = orbit.bandThickness !== void 0 ? orbit.bandThickness / 2 + 4 : orbit.band ? 10 : 3;
|
|
1761
|
+
if (orbit.kind === "circle" && orbit.radius !== void 0) {
|
|
1762
|
+
include(orbit.cx - orbit.radius - strokePadding, orbit.cy - orbit.radius - strokePadding);
|
|
1763
|
+
include(orbit.cx + orbit.radius + strokePadding, orbit.cy + orbit.radius + strokePadding);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const rx = orbit.rx ?? orbit.radius ?? 0;
|
|
1767
|
+
const ry = orbit.ry ?? orbit.radius ?? 0;
|
|
1768
|
+
const points = sampleEllipseArcPoints(orbit.cx, orbit.cy, rx, ry, orbit.rotationDeg, 0, Math.PI * 2, ARC_SAMPLE_COUNT * 2);
|
|
1769
|
+
for (const point of points) {
|
|
1770
|
+
include(point.x - strokePadding, point.y - strokePadding);
|
|
1771
|
+
include(point.x + strokePadding, point.y + strokePadding);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function createBounds(minX, minY, maxX, maxY) {
|
|
1775
|
+
return {
|
|
1776
|
+
minX,
|
|
1777
|
+
minY,
|
|
1778
|
+
maxX,
|
|
1779
|
+
maxY,
|
|
1780
|
+
width: maxX - minX,
|
|
1781
|
+
height: maxY - minY,
|
|
1782
|
+
centerX: minX + (maxX - minX) / 2,
|
|
1783
|
+
centerY: minY + (maxY - minY) / 2
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
function includeObjectBounds(object, include) {
|
|
1787
|
+
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1788
|
+
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1789
|
+
}
|
|
1790
|
+
function includeLabelBounds(label, include) {
|
|
1791
|
+
const labelScale = 1;
|
|
1792
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label.label, label.secondaryLabel, labelScale);
|
|
1793
|
+
include(label.x - labelHalfWidth, label.y - 18);
|
|
1794
|
+
include(label.x + labelHalfWidth, label.y + 8);
|
|
1795
|
+
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
1796
|
+
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
1797
|
+
}
|
|
1798
|
+
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1799
|
+
if (positions.has(object.id)) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
positions.set(object.id, {
|
|
1803
|
+
object,
|
|
1804
|
+
x,
|
|
1805
|
+
y,
|
|
1806
|
+
radius: visualRadiusFor(object, depth, context.scaleModel),
|
|
1807
|
+
sortKey: computeSortKey(x, y, depth)
|
|
1808
|
+
});
|
|
1809
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, depth + 1);
|
|
1810
|
+
}
|
|
1811
|
+
function placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context, depth) {
|
|
1812
|
+
const parent = positions.get(object.id);
|
|
1813
|
+
if (!parent) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const orbiting = [...context.orbitChildren.get(object.id) ?? []].sort(compareOrbiting);
|
|
1817
|
+
const orbitMetricContext = computeOrbitMetricContext(orbiting, parent.radius, context.spacingFactor, context.scaleModel);
|
|
1818
|
+
orbiting.forEach((child, index) => {
|
|
1819
|
+
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, context);
|
|
1820
|
+
orbitDrafts.push({
|
|
1821
|
+
object: child,
|
|
1822
|
+
parentId: object.id,
|
|
1823
|
+
kind: orbitGeometry.kind,
|
|
1824
|
+
cx: orbitGeometry.cx,
|
|
1825
|
+
cy: orbitGeometry.cy,
|
|
1826
|
+
radius: orbitGeometry.radius,
|
|
1827
|
+
rx: orbitGeometry.rx,
|
|
1828
|
+
ry: orbitGeometry.ry,
|
|
1829
|
+
rotationDeg: orbitGeometry.rotationDeg,
|
|
1830
|
+
band: orbitGeometry.band,
|
|
1831
|
+
bandThickness: orbitGeometry.bandThickness,
|
|
1832
|
+
frontArcPath: orbitGeometry.frontArcPath,
|
|
1833
|
+
backArcPath: orbitGeometry.backArcPath
|
|
1834
|
+
});
|
|
1835
|
+
placeObject(child, orbitGeometry.objectX, orbitGeometry.objectY, depth, positions, orbitDrafts, leaderDrafts, context);
|
|
1836
|
+
});
|
|
1837
|
+
const surfaceObjects = [...context.surfaceChildren.get(object.id) ?? []];
|
|
1838
|
+
surfaceObjects.forEach((child, index) => {
|
|
1839
|
+
const angle = angleForIndex(index, surfaceObjects.length, -Math.PI / 3);
|
|
1840
|
+
const leaderDistance = 28 * context.spacingFactor;
|
|
1841
|
+
const anchorOffset = projectPolarOffset(angle, parent.radius, context.projection, context.projection === "isometric" ? 0.9 : 1);
|
|
1842
|
+
const bodyOffset = projectPolarOffset(angle, parent.radius + leaderDistance, context.projection, context.projection === "isometric" ? 0.9 : 1);
|
|
1843
|
+
const anchorX = parent.x + anchorOffset.x;
|
|
1844
|
+
const anchorY = parent.y + anchorOffset.y;
|
|
1845
|
+
const x = parent.x + bodyOffset.x;
|
|
1846
|
+
const y = parent.y + bodyOffset.y;
|
|
1847
|
+
positions.set(child.id, {
|
|
1848
|
+
object: child,
|
|
1849
|
+
x,
|
|
1850
|
+
y,
|
|
1851
|
+
radius: visualRadiusFor(child, depth + 1, context.scaleModel),
|
|
1852
|
+
sortKey: computeSortKey(x, y, depth + 1),
|
|
1853
|
+
anchorX,
|
|
1854
|
+
anchorY
|
|
1855
|
+
});
|
|
1856
|
+
leaderDrafts.push({
|
|
1857
|
+
object: child,
|
|
1858
|
+
groupId: context.objectMap.has(child.id) ? createGroupId(resolveGroupRootObjectId(child, context.objectMap)) : null,
|
|
1859
|
+
x1: anchorX,
|
|
1860
|
+
y1: anchorY,
|
|
1861
|
+
x2: x,
|
|
1862
|
+
y2: y,
|
|
1863
|
+
mode: "surface"
|
|
1864
|
+
});
|
|
1865
|
+
placeOrbitingChildren(child, positions, orbitDrafts, leaderDrafts, context, depth + 1);
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
function compareOrbiting(left, right) {
|
|
1869
|
+
const leftMetric = orbitMetric(left);
|
|
1870
|
+
const rightMetric = orbitMetric(right);
|
|
1871
|
+
if (leftMetric !== null && rightMetric !== null && leftMetric !== rightMetric) {
|
|
1872
|
+
return leftMetric - rightMetric;
|
|
1873
|
+
}
|
|
1874
|
+
if (leftMetric !== null && rightMetric === null)
|
|
1875
|
+
return -1;
|
|
1876
|
+
if (leftMetric === null && rightMetric !== null)
|
|
1877
|
+
return 1;
|
|
1878
|
+
return left.id.localeCompare(right.id);
|
|
1879
|
+
}
|
|
1880
|
+
function computeOrbitMetricContext(objects, parentRadius, spacingFactor, scaleModel) {
|
|
1881
|
+
const metrics = objects.map((object) => orbitMetric(object));
|
|
1882
|
+
const presentMetrics = metrics.filter((value) => value !== null);
|
|
1883
|
+
const innerPx = parentRadius + 56 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
1884
|
+
const stepPx = (objects.length > 2 ? 54 : 64) * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
1885
|
+
if (presentMetrics.length === 0) {
|
|
1886
|
+
return {
|
|
1887
|
+
metrics,
|
|
1888
|
+
minMetric: 0,
|
|
1889
|
+
maxMetric: 0,
|
|
1890
|
+
metricSpread: 0,
|
|
1891
|
+
innerPx,
|
|
1892
|
+
stepPx,
|
|
1893
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
const minMetric = Math.min(...presentMetrics);
|
|
1897
|
+
const maxMetric = Math.max(...presentMetrics);
|
|
1898
|
+
const metricSpread = maxMetric - minMetric;
|
|
1899
|
+
return {
|
|
1900
|
+
metrics,
|
|
1901
|
+
minMetric,
|
|
1902
|
+
maxMetric,
|
|
1903
|
+
metricSpread,
|
|
1904
|
+
innerPx,
|
|
1905
|
+
stepPx,
|
|
1906
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
function resolveOrbitGeometry(object, index, count, parent, metricContext, context) {
|
|
1910
|
+
const placement = object.placement;
|
|
1911
|
+
const band = object.type === "belt" || object.type === "ring";
|
|
1912
|
+
if (!placement || placement.mode !== "orbit") {
|
|
1913
|
+
const fallbackRadius = metricContext.innerPx + index * metricContext.stepPx;
|
|
1914
|
+
return {
|
|
1915
|
+
kind: "circle",
|
|
1916
|
+
cx: parent.x,
|
|
1917
|
+
cy: parent.y,
|
|
1918
|
+
radius: fallbackRadius,
|
|
1919
|
+
rotationDeg: 0,
|
|
1920
|
+
band,
|
|
1921
|
+
bandThickness: band ? 12 * context.scaleModel.ringThicknessMultiplier : void 0,
|
|
1922
|
+
objectX: parent.x,
|
|
1923
|
+
objectY: parent.y - fallbackRadius
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
const eccentricity = clampNumber(typeof placement.eccentricity === "number" ? placement.eccentricity : 0, 0, 0.92);
|
|
1927
|
+
const semiMajor = resolveOrbitRadiusPx(object, index, metricContext);
|
|
1928
|
+
const baseMinor = Math.max(semiMajor * Math.sqrt(1 - eccentricity * eccentricity), semiMajor * 0.18);
|
|
1929
|
+
const inclinationDeg = unitValueToDegrees(placement.inclination) ?? 0;
|
|
1930
|
+
const inclinationScale = context.projection === "isometric" ? Math.max(MIN_ISO_MINOR_SCALE, Math.cos(degreesToRadians(inclinationDeg))) * ISO_FLATTENING : 1;
|
|
1931
|
+
const semiMinor = Math.max(baseMinor * inclinationScale, semiMajor * 0.14);
|
|
1932
|
+
const rotationDeg = unitValueToDegrees(placement.angle) ?? 0;
|
|
1933
|
+
const focusOffset = semiMajor * eccentricity;
|
|
1934
|
+
const centerOffset = rotateOffset(-focusOffset, 0, rotationDeg);
|
|
1935
|
+
const cx = parent.x + centerOffset.x;
|
|
1936
|
+
const cy = parent.y + centerOffset.y;
|
|
1937
|
+
const phase = resolveOrbitPhase(placement.phase, index, count);
|
|
1938
|
+
const objectPoint = ellipsePoint(cx, cy, semiMajor, semiMinor, rotationDeg, phase);
|
|
1939
|
+
const useCircle = context.projection === "topdown" && eccentricity <= 1e-4 && Math.abs(rotationDeg) <= 1e-4;
|
|
1940
|
+
const bandThickness = band ? resolveBandThickness(object, semiMajor, metricContext, context.scaleModel) : void 0;
|
|
1941
|
+
return {
|
|
1942
|
+
kind: useCircle ? "circle" : "ellipse",
|
|
1943
|
+
cx: useCircle ? parent.x : cx,
|
|
1944
|
+
cy: useCircle ? parent.y : cy,
|
|
1945
|
+
radius: useCircle ? semiMajor : void 0,
|
|
1946
|
+
rx: useCircle ? void 0 : semiMajor,
|
|
1947
|
+
ry: useCircle ? void 0 : semiMinor,
|
|
1948
|
+
rotationDeg,
|
|
1949
|
+
band,
|
|
1950
|
+
bandThickness,
|
|
1951
|
+
frontArcPath: context.projection === "isometric" || band ? buildEllipseArcPath(cx, cy, semiMajor, semiMinor, rotationDeg, 0, Math.PI) : void 0,
|
|
1952
|
+
backArcPath: context.projection === "isometric" || band ? buildEllipseArcPath(cx, cy, semiMajor, semiMinor, rotationDeg, Math.PI, Math.PI * 2) : void 0,
|
|
1953
|
+
objectX: objectPoint.x,
|
|
1954
|
+
objectY: objectPoint.y
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
function resolveOrbitRadiusPx(object, index, metricContext) {
|
|
1958
|
+
const metric = orbitMetric(object);
|
|
1959
|
+
if (metric === null) {
|
|
1960
|
+
return metricContext.innerPx + index * metricContext.stepPx;
|
|
1961
|
+
}
|
|
1962
|
+
if (metricContext.metricSpread > 0) {
|
|
1963
|
+
return metricContext.innerPx + (metric - metricContext.minMetric) / metricContext.metricSpread * metricContext.pixelSpread;
|
|
1964
|
+
}
|
|
1965
|
+
return metricContext.innerPx + Math.log10(metric + 1) * metricContext.stepPx;
|
|
1966
|
+
}
|
|
1967
|
+
function orbitMetric(object) {
|
|
1968
|
+
if (!object.placement || object.placement.mode !== "orbit") {
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
|
|
1972
|
+
}
|
|
1973
|
+
function resolveOrbitPhase(phase, index, count) {
|
|
1974
|
+
const degreeValue = phase ? unitValueToDegrees(phase) : null;
|
|
1975
|
+
if (degreeValue !== null) {
|
|
1976
|
+
return degreesToRadians(degreeValue - 90);
|
|
1977
|
+
}
|
|
1978
|
+
return angleForIndex(index, count, -Math.PI / 2);
|
|
1979
|
+
}
|
|
1980
|
+
function resolveBandThickness(object, orbitRadius, metricContext, scaleModel) {
|
|
1981
|
+
const innerMetric = toDistanceMetric(toUnitValue(object.properties.inner));
|
|
1982
|
+
const outerMetric = toDistanceMetric(toUnitValue(object.properties.outer));
|
|
1983
|
+
if (innerMetric !== null && outerMetric !== null) {
|
|
1984
|
+
const thicknessMetric = Math.abs(outerMetric - innerMetric);
|
|
1985
|
+
if (metricContext.metricSpread > 0) {
|
|
1986
|
+
return clampNumber(thicknessMetric / metricContext.metricSpread * metricContext.pixelSpread * scaleModel.ringThicknessMultiplier, 8, 54);
|
|
1987
|
+
}
|
|
1988
|
+
const referenceMetric = Math.max(Math.max(innerMetric, outerMetric), 1e-4);
|
|
1989
|
+
return clampNumber(thicknessMetric / referenceMetric * orbitRadius * 0.75 * scaleModel.ringThicknessMultiplier, 8, 48);
|
|
1990
|
+
}
|
|
1991
|
+
const fallbackBase = object.type === "belt" ? 18 : 12;
|
|
1992
|
+
return fallbackBase * scaleModel.ringThicknessMultiplier;
|
|
1993
|
+
}
|
|
1994
|
+
function resolveAtPosition(reference, positions, objectMap, index, count, width, height, padding, context) {
|
|
1995
|
+
if (reference.kind === "lagrange") {
|
|
1996
|
+
return resolveLagrangePosition(reference, positions, objectMap, width, height);
|
|
1997
|
+
}
|
|
1998
|
+
if (reference.kind === "anchor") {
|
|
1999
|
+
const anchor = positions.get(reference.objectId);
|
|
2000
|
+
if (anchor) {
|
|
2001
|
+
const angle = angleForIndex(index, count, Math.PI / 5);
|
|
2002
|
+
const distance = (anchor.radius + 36) * context.scaleModel.labelMultiplier;
|
|
2003
|
+
const offset = projectPolarOffset(angle, distance, context.projection, context.projection === "isometric" ? 0.92 : 1);
|
|
2004
|
+
return {
|
|
2005
|
+
x: anchor.x + offset.x,
|
|
2006
|
+
y: anchor.y + offset.y,
|
|
2007
|
+
anchorX: anchor.x,
|
|
2008
|
+
anchorY: anchor.y
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (reference.kind === "named") {
|
|
2013
|
+
const anchor = positions.get(reference.name);
|
|
2014
|
+
if (anchor) {
|
|
2015
|
+
const angle = angleForIndex(index, count, Math.PI / 6);
|
|
2016
|
+
const distance = (anchor.radius + 36) * context.scaleModel.labelMultiplier;
|
|
2017
|
+
const offset = projectPolarOffset(angle, distance, context.projection, context.projection === "isometric" ? 0.92 : 1);
|
|
2018
|
+
return {
|
|
2019
|
+
x: anchor.x + offset.x,
|
|
2020
|
+
y: anchor.y + offset.y,
|
|
2021
|
+
anchorX: anchor.x,
|
|
2022
|
+
anchorY: anchor.y
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return {
|
|
2027
|
+
x: width - padding - 170,
|
|
2028
|
+
y: height - padding - 86 - index * 58 * context.scaleModel.freePlacementMultiplier
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function resolveLagrangePosition(reference, positions, objectMap, width, height) {
|
|
2032
|
+
const primary = reference.secondary ? positions.get(reference.primary) : deriveParentAnchor(reference.primary, positions, objectMap);
|
|
2033
|
+
const secondary = positions.get(reference.secondary ?? reference.primary);
|
|
2034
|
+
if (!primary || !secondary) {
|
|
2035
|
+
return {
|
|
2036
|
+
x: width * 0.7,
|
|
2037
|
+
y: height * 0.25
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
const dx = secondary.x - primary.x;
|
|
2041
|
+
const dy = secondary.y - primary.y;
|
|
2042
|
+
const distance = Math.hypot(dx, dy) || 1;
|
|
2043
|
+
const ux = dx / distance;
|
|
2044
|
+
const uy = dy / distance;
|
|
2045
|
+
const nx = -uy;
|
|
2046
|
+
const ny = ux;
|
|
2047
|
+
const offset = clampNumber(distance * 0.25, 24, 68);
|
|
2048
|
+
switch (reference.point) {
|
|
2049
|
+
case "L1":
|
|
2050
|
+
return {
|
|
2051
|
+
x: secondary.x - ux * offset,
|
|
2052
|
+
y: secondary.y - uy * offset,
|
|
2053
|
+
anchorX: secondary.x,
|
|
2054
|
+
anchorY: secondary.y
|
|
2055
|
+
};
|
|
2056
|
+
case "L2":
|
|
2057
|
+
return {
|
|
2058
|
+
x: secondary.x + ux * offset,
|
|
2059
|
+
y: secondary.y + uy * offset,
|
|
2060
|
+
anchorX: secondary.x,
|
|
2061
|
+
anchorY: secondary.y
|
|
2062
|
+
};
|
|
2063
|
+
case "L3":
|
|
2064
|
+
return {
|
|
2065
|
+
x: primary.x - ux * offset,
|
|
2066
|
+
y: primary.y - uy * offset,
|
|
2067
|
+
anchorX: primary.x,
|
|
2068
|
+
anchorY: primary.y
|
|
2069
|
+
};
|
|
2070
|
+
case "L4":
|
|
2071
|
+
return {
|
|
2072
|
+
x: secondary.x + (ux * 0.5 - nx * 0.8660254) * offset,
|
|
2073
|
+
y: secondary.y + (uy * 0.5 - ny * 0.8660254) * offset,
|
|
2074
|
+
anchorX: secondary.x,
|
|
2075
|
+
anchorY: secondary.y
|
|
2076
|
+
};
|
|
2077
|
+
case "L5":
|
|
2078
|
+
return {
|
|
2079
|
+
x: secondary.x + (ux * 0.5 + nx * 0.8660254) * offset,
|
|
2080
|
+
y: secondary.y + (uy * 0.5 + ny * 0.8660254) * offset,
|
|
2081
|
+
anchorX: secondary.x,
|
|
2082
|
+
anchorY: secondary.y
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
function buildSceneRelationships(objects, objectMap) {
|
|
2087
|
+
const parentIds = /* @__PURE__ */ new Map();
|
|
2088
|
+
const childIds = /* @__PURE__ */ new Map();
|
|
2089
|
+
for (const object of objects) {
|
|
2090
|
+
const parentId = resolveParentId(object, objectMap);
|
|
2091
|
+
parentIds.set(object.id, parentId);
|
|
2092
|
+
if (parentId) {
|
|
2093
|
+
const existing = childIds.get(parentId);
|
|
2094
|
+
if (existing) {
|
|
2095
|
+
existing.push(object.id);
|
|
2096
|
+
} else {
|
|
2097
|
+
childIds.set(parentId, [object.id]);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (!childIds.has(object.id)) {
|
|
2101
|
+
childIds.set(object.id, []);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
const ancestorIds = /* @__PURE__ */ new Map();
|
|
2105
|
+
const groupIds = /* @__PURE__ */ new Map();
|
|
2106
|
+
const groupRoots = /* @__PURE__ */ new Map();
|
|
2107
|
+
const buildAncestors = (objectId) => {
|
|
2108
|
+
const cached = ancestorIds.get(objectId);
|
|
2109
|
+
if (cached) {
|
|
2110
|
+
return cached;
|
|
2111
|
+
}
|
|
2112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2113
|
+
const results = [];
|
|
2114
|
+
let cursor = parentIds.get(objectId) ?? null;
|
|
2115
|
+
while (cursor && !seen.has(cursor)) {
|
|
2116
|
+
results.push(cursor);
|
|
2117
|
+
seen.add(cursor);
|
|
2118
|
+
cursor = parentIds.get(cursor) ?? null;
|
|
2119
|
+
}
|
|
2120
|
+
ancestorIds.set(objectId, results);
|
|
2121
|
+
return results;
|
|
2122
|
+
};
|
|
2123
|
+
const resolveGroupRootObjectId2 = (objectId) => {
|
|
2124
|
+
const cached = groupRoots.get(groupIds.get(objectId) ?? "");
|
|
2125
|
+
if (cached) {
|
|
2126
|
+
return cached;
|
|
2127
|
+
}
|
|
2128
|
+
const parentId = parentIds.get(objectId) ?? null;
|
|
2129
|
+
const object = objectMap.get(objectId);
|
|
2130
|
+
let rootObjectId = objectId;
|
|
2131
|
+
if (object?.placement && object.placement.mode !== "free" && parentId) {
|
|
2132
|
+
rootObjectId = resolveGroupRootObjectId2(parentId);
|
|
2133
|
+
}
|
|
2134
|
+
return rootObjectId;
|
|
2135
|
+
};
|
|
2136
|
+
for (const object of objects) {
|
|
2137
|
+
buildAncestors(object.id);
|
|
2138
|
+
const rootObjectId = resolveGroupRootObjectId2(object.id);
|
|
2139
|
+
const groupId = createGroupId(rootObjectId);
|
|
2140
|
+
groupIds.set(object.id, groupId);
|
|
2141
|
+
groupRoots.set(groupId, rootObjectId);
|
|
2142
|
+
}
|
|
2143
|
+
return {
|
|
2144
|
+
parentIds,
|
|
2145
|
+
childIds,
|
|
2146
|
+
ancestorIds,
|
|
2147
|
+
groupIds,
|
|
2148
|
+
groupRoots
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function resolveParentId(object, objectMap) {
|
|
2152
|
+
const placement = object.placement;
|
|
2153
|
+
if (!placement) {
|
|
2154
|
+
return null;
|
|
2155
|
+
}
|
|
2156
|
+
switch (placement.mode) {
|
|
2157
|
+
case "orbit":
|
|
2158
|
+
case "surface":
|
|
2159
|
+
return objectMap.has(placement.target) ? placement.target : null;
|
|
2160
|
+
case "at":
|
|
2161
|
+
switch (placement.reference.kind) {
|
|
2162
|
+
case "anchor":
|
|
2163
|
+
return objectMap.has(placement.reference.objectId) ? placement.reference.objectId : null;
|
|
2164
|
+
case "named":
|
|
2165
|
+
return objectMap.has(placement.reference.name) ? placement.reference.name : null;
|
|
2166
|
+
case "lagrange":
|
|
2167
|
+
if (placement.reference.secondary && objectMap.has(placement.reference.secondary)) {
|
|
2168
|
+
return placement.reference.secondary;
|
|
2169
|
+
}
|
|
2170
|
+
return objectMap.has(placement.reference.primary) ? placement.reference.primary : null;
|
|
2171
|
+
}
|
|
2172
|
+
case "free":
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2177
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
2178
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
2179
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
2180
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
2181
|
+
const include = (x, y) => {
|
|
2182
|
+
minX = Math.min(minX, x);
|
|
2183
|
+
minY = Math.min(minY, y);
|
|
2184
|
+
maxX = Math.max(maxX, x);
|
|
2185
|
+
maxY = Math.max(maxY, y);
|
|
2186
|
+
};
|
|
2187
|
+
for (const object of objects) {
|
|
2188
|
+
if (!object.hidden && group.objectIds.includes(object.objectId)) {
|
|
2189
|
+
includeObjectBounds(object, include);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
for (const orbit of orbitVisuals) {
|
|
2193
|
+
if (!orbit.hidden && group.orbitIds.includes(orbit.objectId)) {
|
|
2194
|
+
includeOrbitBounds(orbit, include);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
for (const leader of leaders) {
|
|
2198
|
+
if (!leader.hidden && group.leaderIds.includes(leader.objectId)) {
|
|
2199
|
+
include(leader.x1, leader.y1);
|
|
2200
|
+
include(leader.x2, leader.y2);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
for (const label of labels) {
|
|
2204
|
+
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2205
|
+
includeLabelBounds(label, include);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
2209
|
+
return createBounds(0, 0, 0, 0);
|
|
2210
|
+
}
|
|
2211
|
+
return createBounds(minX, minY, maxX, maxY);
|
|
2212
|
+
}
|
|
2213
|
+
function resolveGroupRootObjectId(object, objectMap) {
|
|
2214
|
+
let current = object;
|
|
2215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2216
|
+
while (current.placement && current.placement.mode !== "free" && !seen.has(current.id)) {
|
|
2217
|
+
seen.add(current.id);
|
|
2218
|
+
const parentId = resolveParentId(current, objectMap);
|
|
2219
|
+
if (!parentId) {
|
|
2220
|
+
break;
|
|
2221
|
+
}
|
|
2222
|
+
const parent = objectMap.get(parentId);
|
|
2223
|
+
if (!parent) {
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
current = parent;
|
|
2227
|
+
}
|
|
2228
|
+
return current.id;
|
|
2229
|
+
}
|
|
2230
|
+
function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
|
|
2231
|
+
return {
|
|
2232
|
+
left: x - labelHalfWidth,
|
|
2233
|
+
right: x + labelHalfWidth,
|
|
2234
|
+
top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
|
|
2235
|
+
bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12)
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function rectsOverlap(left, right) {
|
|
2239
|
+
return !(left.right < right.left || right.right < left.left || left.bottom < right.top || right.bottom < left.top);
|
|
2240
|
+
}
|
|
2241
|
+
function deriveParentAnchor(objectId, positions, objectMap) {
|
|
2242
|
+
const object = objectMap.get(objectId);
|
|
2243
|
+
if (!object?.placement || object.placement.mode !== "orbit") {
|
|
2244
|
+
return positions.get(objectId);
|
|
2245
|
+
}
|
|
2246
|
+
return positions.get(object.placement.target);
|
|
2247
|
+
}
|
|
2248
|
+
function visualRadiusFor(object, depth, scaleModel) {
|
|
2249
|
+
const explicitRadius = toVisualSizeMetric(object.properties.radius, scaleModel);
|
|
2250
|
+
if (explicitRadius !== null) {
|
|
2251
|
+
return explicitRadius;
|
|
2252
|
+
}
|
|
2253
|
+
const multiplier = scaleModel.bodyRadiusMultiplier;
|
|
2254
|
+
switch (object.type) {
|
|
2255
|
+
case "star":
|
|
2256
|
+
return clampNumber((depth === 0 ? 28 : 20) * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2257
|
+
case "planet":
|
|
2258
|
+
return clampNumber(12 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2259
|
+
case "moon":
|
|
2260
|
+
return clampNumber(7 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2261
|
+
case "belt":
|
|
2262
|
+
return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2263
|
+
case "asteroid":
|
|
2264
|
+
return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2265
|
+
case "comet":
|
|
2266
|
+
return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2267
|
+
case "ring":
|
|
2268
|
+
return clampNumber(5 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2269
|
+
case "structure":
|
|
2270
|
+
return clampNumber(6 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2271
|
+
case "phenomenon":
|
|
2272
|
+
return clampNumber(8 * multiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function visualExtentForObject(object, radius, scaleModel) {
|
|
2276
|
+
const atmosphereBoost = typeof object.properties.atmosphere === "string" ? 4 : 0;
|
|
2277
|
+
switch (object.type) {
|
|
2278
|
+
case "star":
|
|
2279
|
+
return radius * 2.4;
|
|
2280
|
+
case "phenomenon":
|
|
2281
|
+
return radius * 1.25;
|
|
2282
|
+
case "structure":
|
|
2283
|
+
return radius + 2;
|
|
2284
|
+
default:
|
|
2285
|
+
return Math.min(radius + atmosphereBoost, scaleModel.maxBodyRadius + 10);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function toDistanceMetric(value) {
|
|
2289
|
+
if (!value)
|
|
2290
|
+
return null;
|
|
2291
|
+
switch (value.unit) {
|
|
2292
|
+
case "au":
|
|
2293
|
+
return value.value;
|
|
2294
|
+
case "km":
|
|
2295
|
+
return value.value / AU_IN_KM;
|
|
2296
|
+
case "re":
|
|
2297
|
+
return value.value * EARTH_RADIUS_IN_KM / AU_IN_KM;
|
|
2298
|
+
case "sol":
|
|
2299
|
+
return value.value * SOLAR_RADIUS_IN_KM / AU_IN_KM;
|
|
2300
|
+
default:
|
|
2301
|
+
return value.value;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
function freePlacementOffsetPx(distance, scaleModel) {
|
|
2305
|
+
const metric = toDistanceMetric(distance ?? null);
|
|
2306
|
+
if (metric === null || metric <= 0) {
|
|
2307
|
+
return 0;
|
|
2308
|
+
}
|
|
2309
|
+
return clampNumber(metric * 96 * scaleModel.freePlacementMultiplier, 0, 420);
|
|
2310
|
+
}
|
|
2311
|
+
function toVisualSizeMetric(value, scaleModel) {
|
|
2312
|
+
const unitValue = toUnitValue(value);
|
|
2313
|
+
if (!unitValue) {
|
|
2314
|
+
return null;
|
|
2315
|
+
}
|
|
2316
|
+
let size;
|
|
2317
|
+
switch (unitValue.unit) {
|
|
2318
|
+
case "sol":
|
|
2319
|
+
size = clampNumber(unitValue.value * 22, 14, 40);
|
|
2320
|
+
break;
|
|
2321
|
+
case "re":
|
|
2322
|
+
size = clampNumber(unitValue.value * 10, 6, 18);
|
|
2323
|
+
break;
|
|
2324
|
+
case "km":
|
|
2325
|
+
size = clampNumber(Math.log10(Math.max(unitValue.value, 1)) * 2.6, 4, 16);
|
|
2326
|
+
break;
|
|
2327
|
+
default:
|
|
2328
|
+
size = clampNumber(unitValue.value * 4, 4, 20);
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
2331
|
+
return clampNumber(size * scaleModel.bodyRadiusMultiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius);
|
|
2332
|
+
}
|
|
2333
|
+
function toUnitValue(value) {
|
|
2334
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
return value;
|
|
2338
|
+
}
|
|
2339
|
+
function unitValueToDegrees(value) {
|
|
2340
|
+
if (!value) {
|
|
2341
|
+
return null;
|
|
2342
|
+
}
|
|
2343
|
+
return value.unit === "deg" || value.unit === null ? value.value : null;
|
|
2344
|
+
}
|
|
2345
|
+
function angleForIndex(index, count, startAngle) {
|
|
2346
|
+
if (count <= 1)
|
|
2347
|
+
return startAngle;
|
|
2348
|
+
return startAngle + index * Math.PI * 2 / count;
|
|
2349
|
+
}
|
|
2350
|
+
function buildEllipseArcPath(cx, cy, rx, ry, rotationDeg, start, end) {
|
|
2351
|
+
const points = sampleEllipseArcPoints(cx, cy, rx, ry, rotationDeg, start, end, ARC_SAMPLE_COUNT);
|
|
2352
|
+
if (points.length === 0) {
|
|
2353
|
+
return "";
|
|
2354
|
+
}
|
|
2355
|
+
return points.map((point, index) => `${index === 0 ? "M" : "L"} ${formatNumber(point.x)} ${formatNumber(point.y)}`).join(" ");
|
|
2356
|
+
}
|
|
2357
|
+
function sampleEllipseArcPoints(cx, cy, rx, ry, rotationDeg, start, end, segments) {
|
|
2358
|
+
const points = [];
|
|
2359
|
+
for (let index = 0; index <= segments; index += 1) {
|
|
2360
|
+
const t = start + (end - start) * index / segments;
|
|
2361
|
+
points.push(ellipsePoint(cx, cy, rx, ry, rotationDeg, t));
|
|
2362
|
+
}
|
|
2363
|
+
return points;
|
|
2364
|
+
}
|
|
2365
|
+
function ellipsePoint(cx, cy, rx, ry, rotationDeg, angle) {
|
|
2366
|
+
const localX = rx * Math.cos(angle);
|
|
2367
|
+
const localY = ry * Math.sin(angle);
|
|
2368
|
+
const rotated = rotateOffset(localX, localY, rotationDeg);
|
|
2369
|
+
return {
|
|
2370
|
+
x: cx + rotated.x,
|
|
2371
|
+
y: cy + rotated.y
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
function rotateOffset(x, y, rotationDeg) {
|
|
2375
|
+
const radians = degreesToRadians(rotationDeg);
|
|
2376
|
+
return {
|
|
2377
|
+
x: x * Math.cos(radians) - y * Math.sin(radians),
|
|
2378
|
+
y: x * Math.sin(radians) + y * Math.cos(radians)
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
function projectPolarOffset(angle, distance, projection, verticalFactor) {
|
|
2382
|
+
const yScale = projection === "isometric" ? ISO_FLATTENING * verticalFactor : verticalFactor;
|
|
2383
|
+
return {
|
|
2384
|
+
x: Math.cos(angle) * distance,
|
|
2385
|
+
y: Math.sin(angle) * distance * yScale
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
function computeSortKey(x, y, depth) {
|
|
2389
|
+
return y * 1e3 + x + depth * 0.01;
|
|
2390
|
+
}
|
|
2391
|
+
function clampNumber(value, min, max) {
|
|
2392
|
+
return Math.min(Math.max(value, min), max);
|
|
2393
|
+
}
|
|
2394
|
+
function pushGrouped(map, key, value) {
|
|
2395
|
+
const existing = map.get(key);
|
|
2396
|
+
if (existing) {
|
|
2397
|
+
existing.push(value);
|
|
2398
|
+
} else {
|
|
2399
|
+
map.set(key, [value]);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
function createRenderId(objectId) {
|
|
2403
|
+
const normalized = objectId.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "object";
|
|
2404
|
+
return `wo-${normalized}`;
|
|
2405
|
+
}
|
|
2406
|
+
function createGroupId(objectId) {
|
|
2407
|
+
return `${createRenderId(objectId)}-group`;
|
|
2408
|
+
}
|
|
2409
|
+
function customColorFor(value) {
|
|
2410
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2413
|
+
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2414
|
+
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2415
|
+
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2416
|
+
}
|
|
2417
|
+
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2418
|
+
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2419
|
+
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2420
|
+
return Math.max(primaryWidth, secondaryWidth, 24);
|
|
2421
|
+
}
|
|
2422
|
+
function capitalizeLabel(value) {
|
|
2423
|
+
return value.length > 0 ? value[0].toUpperCase() + value.slice(1) : value;
|
|
2424
|
+
}
|
|
2425
|
+
function degreesToRadians(value) {
|
|
2426
|
+
return value * Math.PI / 180;
|
|
2427
|
+
}
|
|
2428
|
+
function formatNumber(value) {
|
|
2429
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// packages/core/dist/draft.js
|
|
2433
|
+
function upgradeDocumentToV2(document, options = {}) {
|
|
2434
|
+
const scene = renderDocumentToScene(document, options);
|
|
2435
|
+
const diagnostics = [];
|
|
2436
|
+
const atlasMetadata = collectAtlasMetadata(document, diagnostics);
|
|
2437
|
+
const annotations = collectDraftAnnotations(document, diagnostics);
|
|
2438
|
+
const defaults = createDraftDefaults(document, scene.renderPreset ?? options.preset ?? null, scene.projection);
|
|
2439
|
+
const system = document.system ? createDraftSystem(document, defaults, atlasMetadata, annotations, diagnostics, scene.renderPreset ?? options.preset ?? null) : null;
|
|
2440
|
+
if (scene.viewpoints.some((viewpoint) => !viewpoint.generated)) {
|
|
2441
|
+
diagnostics.push({
|
|
2442
|
+
code: "upgrade.viewpoints.structured",
|
|
2443
|
+
severity: "info",
|
|
2444
|
+
source: "upgrade",
|
|
2445
|
+
message: `Promoted ${scene.viewpoints.filter((viewpoint) => !viewpoint.generated).length} document-defined viewpoint(s) into the 2.0 atlas section.`
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
return {
|
|
2449
|
+
format: "worldorbit",
|
|
2450
|
+
version: "2.0",
|
|
2451
|
+
sourceVersion: document.version,
|
|
2452
|
+
system,
|
|
2453
|
+
objects: document.objects.map(cloneWorldOrbitObject),
|
|
2454
|
+
diagnostics
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
function upgradeDocumentToDraftV2(document, options = {}) {
|
|
2458
|
+
return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document, options));
|
|
2459
|
+
}
|
|
2460
|
+
function materializeAtlasDocument(document) {
|
|
2461
|
+
const system = document.system ? {
|
|
2462
|
+
type: "system",
|
|
2463
|
+
id: document.system.id,
|
|
2464
|
+
properties: materializeDraftSystemProperties(document.system),
|
|
2465
|
+
info: materializeDraftSystemInfo(document.system)
|
|
2466
|
+
} : null;
|
|
2467
|
+
return {
|
|
2468
|
+
format: "worldorbit",
|
|
2469
|
+
version: "1.0",
|
|
2470
|
+
system,
|
|
2471
|
+
objects: document.objects.map(cloneWorldOrbitObject)
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function materializeDraftDocument(document) {
|
|
2475
|
+
return materializeAtlasDocument(document);
|
|
2476
|
+
}
|
|
2477
|
+
function createDraftSystem(document, defaults, atlasMetadata, annotations, diagnostics, preset) {
|
|
2478
|
+
const scene = renderDocumentToScene(document, {
|
|
2479
|
+
preset: preset ?? void 0,
|
|
2480
|
+
projection: defaults.view
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
type: "system",
|
|
2484
|
+
id: document.system?.id ?? "WorldOrbit",
|
|
2485
|
+
title: typeof document.system?.properties.title === "string" ? document.system.properties.title : null,
|
|
2486
|
+
defaults,
|
|
2487
|
+
atlasMetadata,
|
|
2488
|
+
viewpoints: scene.viewpoints.map(mapSceneViewpointToDraftViewpoint),
|
|
2489
|
+
annotations
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
function createDraftDefaults(document, preset, projection) {
|
|
2493
|
+
return {
|
|
2494
|
+
view: typeof document.system?.properties.view === "string" && document.system.properties.view.toLowerCase() === "topdown" ? "topdown" : projection,
|
|
2495
|
+
scale: typeof document.system?.properties.scale === "string" ? document.system.properties.scale : null,
|
|
2496
|
+
units: typeof document.system?.properties.units === "string" ? document.system.properties.units : null,
|
|
2497
|
+
preset,
|
|
2498
|
+
theme: typeof document.system?.info["atlas.theme"] === "string" ? document.system.info["atlas.theme"] : null
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function collectAtlasMetadata(document, diagnostics) {
|
|
2502
|
+
const metadata = {};
|
|
2503
|
+
for (const [key, value] of Object.entries(document.system?.info ?? {})) {
|
|
2504
|
+
if (key.startsWith("viewpoint.") || key.startsWith("annotation.")) {
|
|
2505
|
+
continue;
|
|
2506
|
+
}
|
|
2507
|
+
metadata[key] = value;
|
|
2508
|
+
}
|
|
2509
|
+
const metadataKeys = Object.keys(metadata);
|
|
2510
|
+
if (metadataKeys.length > 0) {
|
|
2511
|
+
diagnostics.push({
|
|
2512
|
+
code: "upgrade.atlasMetadata.preserved",
|
|
2513
|
+
severity: "warning",
|
|
2514
|
+
source: "upgrade",
|
|
2515
|
+
message: `Preserved ${metadataKeys.length} system info entr${metadataKeys.length === 1 ? "y" : "ies"} as atlas metadata in the 2.0 atlas document.`
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
return metadata;
|
|
2519
|
+
}
|
|
2520
|
+
function collectDraftAnnotations(document, diagnostics) {
|
|
2521
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
2522
|
+
for (const [key, value] of Object.entries(document.system?.info ?? {})) {
|
|
2523
|
+
if (!key.startsWith("annotation.")) {
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
const [, rawId, ...fieldParts] = key.split(".");
|
|
2527
|
+
if (!rawId || fieldParts.length === 0) {
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
const id = normalizeIdentifier(rawId);
|
|
2531
|
+
if (!id) {
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
const draft = drafts.get(id) ?? { id };
|
|
2535
|
+
const field = fieldParts.join(".").toLowerCase();
|
|
2536
|
+
switch (field) {
|
|
2537
|
+
case "label":
|
|
2538
|
+
draft.label = value;
|
|
2539
|
+
break;
|
|
2540
|
+
case "target":
|
|
2541
|
+
case "object":
|
|
2542
|
+
draft.targetObjectId = value.trim() || null;
|
|
2543
|
+
break;
|
|
2544
|
+
case "body":
|
|
2545
|
+
case "text":
|
|
2546
|
+
case "description":
|
|
2547
|
+
draft.body = value;
|
|
2548
|
+
break;
|
|
2549
|
+
case "tags":
|
|
2550
|
+
draft.tags = splitList(value);
|
|
2551
|
+
break;
|
|
2552
|
+
}
|
|
2553
|
+
drafts.set(id, draft);
|
|
2554
|
+
}
|
|
2555
|
+
for (const object of document.objects) {
|
|
2556
|
+
const description = object.info.description;
|
|
2557
|
+
if (!description) {
|
|
2558
|
+
continue;
|
|
2559
|
+
}
|
|
2560
|
+
const annotationId = normalizeIdentifier(`${object.id}-notes`);
|
|
2561
|
+
if (drafts.has(annotationId)) {
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
drafts.set(annotationId, {
|
|
2565
|
+
id: annotationId,
|
|
2566
|
+
label: `${object.id} Notes`,
|
|
2567
|
+
targetObjectId: object.id,
|
|
2568
|
+
body: description,
|
|
2569
|
+
tags: Array.isArray(object.properties.tags) ? object.properties.tags.filter((entry) => typeof entry === "string") : []
|
|
2570
|
+
});
|
|
2571
|
+
diagnostics.push({
|
|
2572
|
+
code: "upgrade.annotation.objectDescription",
|
|
2573
|
+
severity: "info",
|
|
2574
|
+
source: "upgrade",
|
|
2575
|
+
message: `Lifted ${object.id}.info.description into structured atlas annotation "${annotationId}".`,
|
|
2576
|
+
objectId: object.id,
|
|
2577
|
+
field: "description"
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
return [...drafts.values()].filter((draft) => draft.body || draft.label).map((draft) => ({
|
|
2581
|
+
id: draft.id,
|
|
2582
|
+
label: draft.label ?? humanizeIdentifier2(draft.id),
|
|
2583
|
+
targetObjectId: draft.targetObjectId ?? null,
|
|
2584
|
+
body: draft.body ?? "",
|
|
2585
|
+
tags: draft.tags ?? [],
|
|
2586
|
+
sourceObjectId: draft.targetObjectId ?? null
|
|
2587
|
+
})).sort((left, right) => left.label.localeCompare(right.label));
|
|
2588
|
+
}
|
|
2589
|
+
function mapSceneViewpointToDraftViewpoint(viewpoint) {
|
|
2590
|
+
return {
|
|
2591
|
+
id: viewpoint.id,
|
|
2592
|
+
label: viewpoint.label,
|
|
2593
|
+
summary: viewpoint.summary,
|
|
2594
|
+
focusObjectId: viewpoint.objectId,
|
|
2595
|
+
selectedObjectId: viewpoint.selectedObjectId,
|
|
2596
|
+
projection: viewpoint.projection,
|
|
2597
|
+
preset: viewpoint.preset,
|
|
2598
|
+
zoom: viewpoint.scale,
|
|
2599
|
+
rotationDeg: viewpoint.rotationDeg,
|
|
2600
|
+
layers: { ...viewpoint.layers },
|
|
2601
|
+
filter: viewpoint.filter ? {
|
|
2602
|
+
query: viewpoint.filter.query,
|
|
2603
|
+
objectTypes: [...viewpoint.filter.objectTypes],
|
|
2604
|
+
tags: [...viewpoint.filter.tags],
|
|
2605
|
+
groupIds: [...viewpoint.filter.groupIds]
|
|
2606
|
+
} : null
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
function cloneWorldOrbitObject(object) {
|
|
2610
|
+
return {
|
|
2611
|
+
...object,
|
|
2612
|
+
properties: cloneProperties(object.properties),
|
|
2613
|
+
placement: object.placement ? structuredClone(object.placement) : null,
|
|
2614
|
+
info: { ...object.info }
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function cloneProperties(properties) {
|
|
2618
|
+
const next = {};
|
|
2619
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
2620
|
+
if (Array.isArray(value)) {
|
|
2621
|
+
next[key] = [...value];
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
if (value && typeof value === "object" && "value" in value) {
|
|
2625
|
+
next[key] = {
|
|
2626
|
+
value: value.value,
|
|
2627
|
+
unit: value.unit
|
|
2628
|
+
};
|
|
2629
|
+
continue;
|
|
2630
|
+
}
|
|
2631
|
+
next[key] = value;
|
|
2632
|
+
}
|
|
2633
|
+
return next;
|
|
2634
|
+
}
|
|
2635
|
+
function splitList(value) {
|
|
2636
|
+
return value.split(/[\s,]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
2637
|
+
}
|
|
2638
|
+
function normalizeIdentifier(value) {
|
|
2639
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2640
|
+
}
|
|
2641
|
+
function humanizeIdentifier2(value) {
|
|
2642
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
2643
|
+
}
|
|
2644
|
+
function materializeDraftSystemProperties(system) {
|
|
2645
|
+
const properties = {};
|
|
2646
|
+
if (system.title) {
|
|
2647
|
+
properties.title = system.title;
|
|
2648
|
+
}
|
|
2649
|
+
properties.view = system.defaults.view;
|
|
2650
|
+
if (system.defaults.scale) {
|
|
2651
|
+
properties.scale = system.defaults.scale;
|
|
2652
|
+
}
|
|
2653
|
+
if (system.defaults.units) {
|
|
2654
|
+
properties.units = system.defaults.units;
|
|
2655
|
+
}
|
|
2656
|
+
return properties;
|
|
2657
|
+
}
|
|
2658
|
+
function materializeDraftSystemInfo(system) {
|
|
2659
|
+
const info = {
|
|
2660
|
+
...system.atlasMetadata
|
|
2661
|
+
};
|
|
2662
|
+
if (system.defaults.theme) {
|
|
2663
|
+
info["atlas.theme"] = system.defaults.theme;
|
|
2664
|
+
}
|
|
2665
|
+
for (const viewpoint of system.viewpoints) {
|
|
2666
|
+
const prefix = `viewpoint.${viewpoint.id}`;
|
|
2667
|
+
info[`${prefix}.label`] = viewpoint.label;
|
|
2668
|
+
if (viewpoint.summary) {
|
|
2669
|
+
info[`${prefix}.summary`] = viewpoint.summary;
|
|
2670
|
+
}
|
|
2671
|
+
if (viewpoint.focusObjectId) {
|
|
2672
|
+
info[`${prefix}.focus`] = viewpoint.focusObjectId;
|
|
2673
|
+
}
|
|
2674
|
+
if (viewpoint.selectedObjectId) {
|
|
2675
|
+
info[`${prefix}.select`] = viewpoint.selectedObjectId;
|
|
2676
|
+
}
|
|
2677
|
+
if (viewpoint.projection) {
|
|
2678
|
+
info[`${prefix}.projection`] = viewpoint.projection;
|
|
2679
|
+
}
|
|
2680
|
+
if (viewpoint.preset) {
|
|
2681
|
+
info[`${prefix}.preset`] = viewpoint.preset;
|
|
2682
|
+
}
|
|
2683
|
+
if (viewpoint.zoom !== null) {
|
|
2684
|
+
info[`${prefix}.zoom`] = String(viewpoint.zoom);
|
|
2685
|
+
}
|
|
2686
|
+
if (viewpoint.rotationDeg !== 0) {
|
|
2687
|
+
info[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2688
|
+
}
|
|
2689
|
+
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2690
|
+
if (serializedLayers) {
|
|
2691
|
+
info[`${prefix}.layers`] = serializedLayers;
|
|
2692
|
+
}
|
|
2693
|
+
if (viewpoint.filter?.query) {
|
|
2694
|
+
info[`${prefix}.query`] = viewpoint.filter.query;
|
|
2695
|
+
}
|
|
2696
|
+
if ((viewpoint.filter?.objectTypes.length ?? 0) > 0) {
|
|
2697
|
+
info[`${prefix}.types`] = viewpoint.filter?.objectTypes.join(" ") ?? "";
|
|
2698
|
+
}
|
|
2699
|
+
if ((viewpoint.filter?.tags.length ?? 0) > 0) {
|
|
2700
|
+
info[`${prefix}.tags`] = viewpoint.filter?.tags.join(" ") ?? "";
|
|
2701
|
+
}
|
|
2702
|
+
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2703
|
+
info[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
for (const annotation of system.annotations) {
|
|
2707
|
+
const prefix = `annotation.${annotation.id}`;
|
|
2708
|
+
info[`${prefix}.label`] = annotation.label;
|
|
2709
|
+
if (annotation.targetObjectId) {
|
|
2710
|
+
info[`${prefix}.target`] = annotation.targetObjectId;
|
|
2711
|
+
}
|
|
2712
|
+
info[`${prefix}.body`] = annotation.body;
|
|
2713
|
+
if (annotation.tags.length > 0) {
|
|
2714
|
+
info[`${prefix}.tags`] = annotation.tags.join(" ");
|
|
2715
|
+
}
|
|
2716
|
+
if (annotation.sourceObjectId) {
|
|
2717
|
+
info[`${prefix}.source`] = annotation.sourceObjectId;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return info;
|
|
2721
|
+
}
|
|
2722
|
+
function serializeViewpointLayers(layers) {
|
|
2723
|
+
const tokens = [];
|
|
2724
|
+
const orbitFront = layers["orbits-front"];
|
|
2725
|
+
const orbitBack = layers["orbits-back"];
|
|
2726
|
+
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2727
|
+
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2728
|
+
}
|
|
2729
|
+
for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
|
|
2730
|
+
if (layers[key] !== void 0) {
|
|
2731
|
+
tokens.push(layers[key] ? key : `-${key}`);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
return tokens.join(" ");
|
|
2735
|
+
}
|
|
2736
|
+
function convertAtlasDocumentToLegacyDraft(document) {
|
|
2737
|
+
return {
|
|
2738
|
+
...document,
|
|
2739
|
+
version: "2.0-draft"
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
// packages/core/dist/format.js
|
|
2744
|
+
var CANONICAL_FIELD_ORDER = [
|
|
2745
|
+
"title",
|
|
2746
|
+
"view",
|
|
2747
|
+
"scale",
|
|
2748
|
+
"units",
|
|
2749
|
+
"kind",
|
|
2750
|
+
"class",
|
|
2751
|
+
"tags",
|
|
2752
|
+
"color",
|
|
2753
|
+
"image",
|
|
2754
|
+
"hidden",
|
|
2755
|
+
"orbit",
|
|
2756
|
+
"distance",
|
|
2757
|
+
"semiMajor",
|
|
2758
|
+
"eccentricity",
|
|
2759
|
+
"period",
|
|
2760
|
+
"angle",
|
|
2761
|
+
"inclination",
|
|
2762
|
+
"phase",
|
|
2763
|
+
"at",
|
|
2764
|
+
"surface",
|
|
2765
|
+
"free",
|
|
2766
|
+
"radius",
|
|
2767
|
+
"mass",
|
|
2768
|
+
"density",
|
|
2769
|
+
"gravity",
|
|
2770
|
+
"temperature",
|
|
2771
|
+
"albedo",
|
|
2772
|
+
"atmosphere",
|
|
2773
|
+
"inner",
|
|
2774
|
+
"outer",
|
|
2775
|
+
"on",
|
|
2776
|
+
"source",
|
|
2777
|
+
"cycle"
|
|
2778
|
+
];
|
|
2779
|
+
function formatDocument(document, options = {}) {
|
|
2780
|
+
const schema = options.schema ?? "auto";
|
|
2781
|
+
const useDraft = schema === "2.0" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.0-draft";
|
|
2782
|
+
if (useDraft) {
|
|
2783
|
+
if (schema === "2.0-draft") {
|
|
2784
|
+
const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" ? {
|
|
2785
|
+
...document,
|
|
2786
|
+
version: "2.0-draft"
|
|
2787
|
+
} : upgradeDocumentToDraftV2(document);
|
|
2788
|
+
return formatDraftDocument(legacyDraftDocument);
|
|
2789
|
+
}
|
|
2790
|
+
const atlasDocument = document.version === "2.0" ? document : document.version === "2.0-draft" ? {
|
|
2791
|
+
...document,
|
|
2792
|
+
version: "2.0"
|
|
2793
|
+
} : upgradeDocumentToV2(document);
|
|
2794
|
+
return formatAtlasDocument(atlasDocument);
|
|
2795
|
+
}
|
|
2796
|
+
const lines = [];
|
|
2797
|
+
const stableDocument = document;
|
|
2798
|
+
if (stableDocument.system) {
|
|
2799
|
+
lines.push(...formatSystem(stableDocument.system));
|
|
2800
|
+
}
|
|
2801
|
+
const sortedObjects = [...stableDocument.objects].sort(compareObjects);
|
|
2802
|
+
for (const object of sortedObjects) {
|
|
2803
|
+
if (lines.length > 0) {
|
|
2804
|
+
lines.push("");
|
|
2805
|
+
}
|
|
2806
|
+
lines.push(...formatObject(object));
|
|
2807
|
+
}
|
|
2808
|
+
return lines.join("\n");
|
|
2809
|
+
}
|
|
2810
|
+
function formatAtlasDocument(document) {
|
|
2811
|
+
const lines = ["schema 2.0", ""];
|
|
2812
|
+
if (document.system) {
|
|
2813
|
+
lines.push(...formatAtlasSystem(document.system));
|
|
2814
|
+
}
|
|
2815
|
+
const sortedObjects = [...document.objects].sort(compareObjects);
|
|
2816
|
+
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2817
|
+
lines.push("");
|
|
2818
|
+
}
|
|
2819
|
+
sortedObjects.forEach((object, index) => {
|
|
2820
|
+
if (index > 0) {
|
|
2821
|
+
lines.push("");
|
|
2822
|
+
}
|
|
2823
|
+
lines.push(...formatAtlasObject(object));
|
|
2824
|
+
});
|
|
2825
|
+
return lines.join("\n");
|
|
2826
|
+
}
|
|
2827
|
+
function formatDraftDocument(document) {
|
|
2828
|
+
const legacy = document.version === "2.0-draft" ? document : {
|
|
2829
|
+
...document,
|
|
2830
|
+
version: "2.0-draft"
|
|
2831
|
+
};
|
|
2832
|
+
const lines = ["schema 2.0-draft", ""];
|
|
2833
|
+
if (legacy.system) {
|
|
2834
|
+
lines.push(...formatAtlasSystem(legacy.system));
|
|
2835
|
+
}
|
|
2836
|
+
const sortedObjects = [...legacy.objects].sort(compareObjects);
|
|
2837
|
+
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2838
|
+
lines.push("");
|
|
2839
|
+
}
|
|
2840
|
+
sortedObjects.forEach((object, index) => {
|
|
2841
|
+
if (index > 0) {
|
|
2842
|
+
lines.push("");
|
|
2843
|
+
}
|
|
2844
|
+
lines.push(...formatAtlasObject(object));
|
|
2845
|
+
});
|
|
2846
|
+
return lines.join("\n");
|
|
2847
|
+
}
|
|
2848
|
+
function formatSystem(system) {
|
|
2849
|
+
return formatLines("system", system.id, system.properties, null, system.info);
|
|
2850
|
+
}
|
|
2851
|
+
function formatAtlasSystem(system) {
|
|
2852
|
+
const lines = [`system ${system.id}`];
|
|
2853
|
+
if (system.title) {
|
|
2854
|
+
lines.push(` title ${quoteIfNeeded(system.title)}`);
|
|
2855
|
+
}
|
|
2856
|
+
lines.push("");
|
|
2857
|
+
lines.push("defaults");
|
|
2858
|
+
lines.push(` view ${system.defaults.view}`);
|
|
2859
|
+
if (system.defaults.scale) {
|
|
2860
|
+
lines.push(` scale ${quoteIfNeeded(system.defaults.scale)}`);
|
|
2861
|
+
}
|
|
2862
|
+
if (system.defaults.units) {
|
|
2863
|
+
lines.push(` units ${quoteIfNeeded(system.defaults.units)}`);
|
|
2864
|
+
}
|
|
2865
|
+
if (system.defaults.preset) {
|
|
2866
|
+
lines.push(` preset ${system.defaults.preset}`);
|
|
2867
|
+
}
|
|
2868
|
+
if (system.defaults.theme) {
|
|
2869
|
+
lines.push(` theme ${quoteIfNeeded(system.defaults.theme)}`);
|
|
2870
|
+
}
|
|
2871
|
+
if (Object.keys(system.atlasMetadata).length > 0) {
|
|
2872
|
+
lines.push("");
|
|
2873
|
+
lines.push("atlas");
|
|
2874
|
+
lines.push(" metadata");
|
|
2875
|
+
for (const [key, value] of Object.entries(system.atlasMetadata).sort(([left], [right]) => left.localeCompare(right))) {
|
|
2876
|
+
lines.push(` ${key} ${quoteIfNeeded(value)}`);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
for (const viewpoint of system.viewpoints) {
|
|
2880
|
+
lines.push("");
|
|
2881
|
+
lines.push(...formatAtlasViewpoint(viewpoint));
|
|
2882
|
+
}
|
|
2883
|
+
for (const annotation of system.annotations) {
|
|
2884
|
+
lines.push("");
|
|
2885
|
+
lines.push(...formatAtlasAnnotation(annotation));
|
|
2886
|
+
}
|
|
2887
|
+
return lines;
|
|
2888
|
+
}
|
|
2889
|
+
function formatObject(object) {
|
|
2890
|
+
return formatLines(object.type, object.id, object.properties, object.placement, object.info);
|
|
2891
|
+
}
|
|
2892
|
+
function formatAtlasObject(object) {
|
|
2893
|
+
return formatLines(`object ${object.type}`, object.id, object.properties, object.placement, object.info);
|
|
2894
|
+
}
|
|
2895
|
+
function formatLines(objectType, id, properties, placement, info) {
|
|
2896
|
+
const lines = [`${objectType} ${id}`];
|
|
2897
|
+
const fieldLines = [...formatPlacement(placement), ...formatProperties(properties)];
|
|
2898
|
+
for (const fieldLine of fieldLines) {
|
|
2899
|
+
lines.push(` ${fieldLine}`);
|
|
2900
|
+
}
|
|
2901
|
+
const infoEntries = Object.entries(info).sort(([left], [right]) => left.localeCompare(right));
|
|
2902
|
+
if (infoEntries.length > 0) {
|
|
2903
|
+
if (fieldLines.length > 0) {
|
|
2904
|
+
lines.push("");
|
|
2905
|
+
}
|
|
2906
|
+
lines.push(" info");
|
|
2907
|
+
for (const [key, value] of infoEntries) {
|
|
2908
|
+
lines.push(` ${key} ${quoteIfNeeded(value)}`);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
return lines;
|
|
2912
|
+
}
|
|
2913
|
+
function formatPlacement(placement) {
|
|
2914
|
+
if (!placement)
|
|
2915
|
+
return [];
|
|
2916
|
+
switch (placement.mode) {
|
|
2917
|
+
case "orbit":
|
|
2918
|
+
return [
|
|
2919
|
+
`orbit ${placement.target}`,
|
|
2920
|
+
...formatOptionalUnit("distance", placement.distance),
|
|
2921
|
+
...formatOptionalUnit("semiMajor", placement.semiMajor),
|
|
2922
|
+
...formatOptionalNumber("eccentricity", placement.eccentricity),
|
|
2923
|
+
...formatOptionalUnit("period", placement.period),
|
|
2924
|
+
...formatOptionalUnit("angle", placement.angle),
|
|
2925
|
+
...formatOptionalUnit("inclination", placement.inclination),
|
|
2926
|
+
...formatOptionalUnit("phase", placement.phase)
|
|
2927
|
+
];
|
|
2928
|
+
case "at":
|
|
2929
|
+
return [`at ${formatAtReference(placement.reference)}`];
|
|
2930
|
+
case "surface":
|
|
2931
|
+
return [`surface ${placement.target}`];
|
|
2932
|
+
case "free":
|
|
2933
|
+
return [`free ${placement.distance ? formatUnitValue(placement.distance) : placement.descriptor ?? ""}`.trim()];
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
function formatProperties(properties) {
|
|
2937
|
+
return Object.keys(properties).sort(compareFieldKeys).map((key) => `${key} ${formatValue(properties[key])}`);
|
|
2938
|
+
}
|
|
2939
|
+
function formatAtlasViewpoint(viewpoint) {
|
|
2940
|
+
const lines = [`viewpoint ${viewpoint.id}`, ` label ${quoteIfNeeded(viewpoint.label)}`];
|
|
2941
|
+
if (viewpoint.focusObjectId) {
|
|
2942
|
+
lines.push(` focus ${viewpoint.focusObjectId}`);
|
|
2943
|
+
}
|
|
2944
|
+
if (viewpoint.selectedObjectId && viewpoint.selectedObjectId !== viewpoint.focusObjectId) {
|
|
2945
|
+
lines.push(` select ${viewpoint.selectedObjectId}`);
|
|
2946
|
+
}
|
|
2947
|
+
if (viewpoint.summary) {
|
|
2948
|
+
lines.push(` summary ${quoteIfNeeded(viewpoint.summary)}`);
|
|
2949
|
+
}
|
|
2950
|
+
if (viewpoint.projection) {
|
|
2951
|
+
lines.push(` projection ${viewpoint.projection}`);
|
|
2952
|
+
}
|
|
2953
|
+
if (viewpoint.preset) {
|
|
2954
|
+
lines.push(` preset ${viewpoint.preset}`);
|
|
2955
|
+
}
|
|
2956
|
+
if (viewpoint.zoom !== null) {
|
|
2957
|
+
lines.push(` zoom ${viewpoint.zoom}`);
|
|
2958
|
+
}
|
|
2959
|
+
if (viewpoint.rotationDeg !== 0) {
|
|
2960
|
+
lines.push(` rotation ${viewpoint.rotationDeg}`);
|
|
2961
|
+
}
|
|
2962
|
+
const layerTokens = formatDraftLayers(viewpoint.layers);
|
|
2963
|
+
if (layerTokens.length > 0) {
|
|
2964
|
+
lines.push(` layers ${layerTokens.join(" ")}`);
|
|
2965
|
+
}
|
|
2966
|
+
if (viewpoint.filter) {
|
|
2967
|
+
lines.push(" filter");
|
|
2968
|
+
if (viewpoint.filter.query) {
|
|
2969
|
+
lines.push(` query ${quoteIfNeeded(viewpoint.filter.query)}`);
|
|
2970
|
+
}
|
|
2971
|
+
if (viewpoint.filter.objectTypes.length > 0) {
|
|
2972
|
+
lines.push(` objectTypes ${viewpoint.filter.objectTypes.join(" ")}`);
|
|
2973
|
+
}
|
|
2974
|
+
if (viewpoint.filter.tags.length > 0) {
|
|
2975
|
+
lines.push(` tags ${viewpoint.filter.tags.map(quoteIfNeeded).join(" ")}`);
|
|
2976
|
+
}
|
|
2977
|
+
if (viewpoint.filter.groupIds.length > 0) {
|
|
2978
|
+
lines.push(` groups ${viewpoint.filter.groupIds.join(" ")}`);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
return lines;
|
|
2982
|
+
}
|
|
2983
|
+
function formatAtlasAnnotation(annotation) {
|
|
2984
|
+
const lines = [`annotation ${annotation.id}`, ` label ${quoteIfNeeded(annotation.label)}`];
|
|
2985
|
+
if (annotation.targetObjectId) {
|
|
2986
|
+
lines.push(` target ${annotation.targetObjectId}`);
|
|
2987
|
+
}
|
|
2988
|
+
lines.push(` body ${quoteIfNeeded(annotation.body)}`);
|
|
2989
|
+
if (annotation.tags.length > 0) {
|
|
2990
|
+
lines.push(` tags ${annotation.tags.map(quoteIfNeeded).join(" ")}`);
|
|
2991
|
+
}
|
|
2992
|
+
return lines;
|
|
2993
|
+
}
|
|
2994
|
+
function formatValue(value) {
|
|
2995
|
+
if (Array.isArray(value)) {
|
|
2996
|
+
return value.map((item) => quoteIfNeeded(item)).join(" ");
|
|
2997
|
+
}
|
|
2998
|
+
if (typeof value === "boolean") {
|
|
2999
|
+
return value ? "true" : "false";
|
|
3000
|
+
}
|
|
3001
|
+
if (typeof value === "number") {
|
|
3002
|
+
return String(value);
|
|
3003
|
+
}
|
|
3004
|
+
if (typeof value === "string") {
|
|
3005
|
+
return quoteIfNeeded(value);
|
|
3006
|
+
}
|
|
3007
|
+
return formatUnitValue(value);
|
|
3008
|
+
}
|
|
3009
|
+
function formatUnitValue(value) {
|
|
3010
|
+
return `${value.value}${value.unit ?? ""}`;
|
|
3011
|
+
}
|
|
3012
|
+
function formatOptionalUnit(key, value) {
|
|
3013
|
+
return value ? [`${key} ${formatUnitValue(value)}`] : [];
|
|
3014
|
+
}
|
|
3015
|
+
function formatOptionalNumber(key, value) {
|
|
3016
|
+
return value === void 0 ? [] : [`${key} ${value}`];
|
|
3017
|
+
}
|
|
3018
|
+
function formatAtReference(reference) {
|
|
3019
|
+
switch (reference.kind) {
|
|
3020
|
+
case "lagrange":
|
|
3021
|
+
return reference.secondary ? `${reference.primary}-${reference.secondary}:${reference.point}` : `${reference.primary}:${reference.point}`;
|
|
3022
|
+
case "anchor":
|
|
3023
|
+
return `${reference.objectId}:${reference.anchor}`;
|
|
3024
|
+
case "named":
|
|
3025
|
+
return reference.name;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
function formatDraftLayers(layers) {
|
|
3029
|
+
const tokens = [];
|
|
3030
|
+
const orbitFront = layers["orbits-front"];
|
|
3031
|
+
const orbitBack = layers["orbits-back"];
|
|
3032
|
+
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
3033
|
+
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
3034
|
+
}
|
|
3035
|
+
for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
|
|
3036
|
+
if (layers[key] !== void 0) {
|
|
3037
|
+
tokens.push(layers[key] ? key : `-${key}`);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
return tokens;
|
|
3041
|
+
}
|
|
3042
|
+
function compareFieldKeys(left, right) {
|
|
3043
|
+
const leftIndex = CANONICAL_FIELD_ORDER.indexOf(left);
|
|
3044
|
+
const rightIndex = CANONICAL_FIELD_ORDER.indexOf(right);
|
|
3045
|
+
if (leftIndex === -1 && rightIndex === -1)
|
|
3046
|
+
return left.localeCompare(right);
|
|
3047
|
+
if (leftIndex === -1)
|
|
3048
|
+
return 1;
|
|
3049
|
+
if (rightIndex === -1)
|
|
3050
|
+
return -1;
|
|
3051
|
+
return leftIndex - rightIndex;
|
|
3052
|
+
}
|
|
3053
|
+
function compareObjects(left, right) {
|
|
3054
|
+
const leftIndex = objectTypeIndex(left.type);
|
|
3055
|
+
const rightIndex = objectTypeIndex(right.type);
|
|
3056
|
+
if (leftIndex !== rightIndex)
|
|
3057
|
+
return leftIndex - rightIndex;
|
|
3058
|
+
return left.id.localeCompare(right.id);
|
|
3059
|
+
}
|
|
3060
|
+
function objectTypeIndex(objectType) {
|
|
3061
|
+
switch (objectType) {
|
|
3062
|
+
case "star":
|
|
3063
|
+
return 0;
|
|
3064
|
+
case "planet":
|
|
3065
|
+
return 1;
|
|
3066
|
+
case "moon":
|
|
3067
|
+
return 2;
|
|
3068
|
+
case "belt":
|
|
3069
|
+
return 3;
|
|
3070
|
+
case "asteroid":
|
|
3071
|
+
return 4;
|
|
3072
|
+
case "comet":
|
|
3073
|
+
return 5;
|
|
3074
|
+
case "ring":
|
|
3075
|
+
return 6;
|
|
3076
|
+
case "structure":
|
|
3077
|
+
return 7;
|
|
3078
|
+
case "phenomenon":
|
|
3079
|
+
return 8;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
function quoteIfNeeded(value) {
|
|
3083
|
+
if (!/\s/.test(value) && !value.includes('"')) {
|
|
3084
|
+
return value;
|
|
3085
|
+
}
|
|
3086
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
// packages/core/dist/draft-parse.js
|
|
3090
|
+
function parseWorldOrbitAtlas(source) {
|
|
3091
|
+
return parseAtlasSource(source, "2.0");
|
|
3092
|
+
}
|
|
3093
|
+
function parseWorldOrbitDraft(source) {
|
|
3094
|
+
return parseAtlasSource(source, "2.0-draft");
|
|
3095
|
+
}
|
|
3096
|
+
function parseAtlasSource(source, outputVersion) {
|
|
3097
|
+
const lines = source.split(/\r?\n/);
|
|
3098
|
+
let sawSchemaHeader = false;
|
|
3099
|
+
let schemaVersion = "2.0";
|
|
3100
|
+
let system = null;
|
|
3101
|
+
let section = null;
|
|
3102
|
+
const objectNodes = [];
|
|
3103
|
+
let sawDefaults = false;
|
|
3104
|
+
let sawAtlas = false;
|
|
3105
|
+
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3106
|
+
const annotationIds = /* @__PURE__ */ new Set();
|
|
3107
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3108
|
+
const rawLine = lines[index];
|
|
3109
|
+
const lineNumber = index + 1;
|
|
3110
|
+
if (!rawLine.trim()) {
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
const indent = getIndent(rawLine);
|
|
3114
|
+
const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
|
|
3115
|
+
line: lineNumber,
|
|
3116
|
+
columnOffset: indent
|
|
3117
|
+
});
|
|
3118
|
+
if (tokens.length === 0) {
|
|
3119
|
+
continue;
|
|
3120
|
+
}
|
|
3121
|
+
if (!sawSchemaHeader) {
|
|
3122
|
+
schemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3123
|
+
sawSchemaHeader = true;
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
if (indent === 0) {
|
|
3127
|
+
section = startTopLevelSection(tokens, lineNumber, system, objectNodes, viewpointIds, annotationIds, {
|
|
3128
|
+
sawDefaults,
|
|
3129
|
+
sawAtlas
|
|
3130
|
+
});
|
|
3131
|
+
if (section.kind === "system") {
|
|
3132
|
+
system = section.system;
|
|
3133
|
+
} else if (section.kind === "defaults") {
|
|
3134
|
+
sawDefaults = true;
|
|
3135
|
+
} else if (section.kind === "atlas") {
|
|
3136
|
+
sawAtlas = true;
|
|
3137
|
+
}
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
if (!section) {
|
|
3141
|
+
throw new WorldOrbitError("Indented line without parent atlas section", lineNumber, indent + 1);
|
|
3142
|
+
}
|
|
3143
|
+
handleSectionLine(section, indent, tokens, lineNumber);
|
|
3144
|
+
}
|
|
3145
|
+
if (!sawSchemaHeader) {
|
|
3146
|
+
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3147
|
+
}
|
|
3148
|
+
const ast = {
|
|
3149
|
+
type: "document",
|
|
3150
|
+
objects: objectNodes
|
|
3151
|
+
};
|
|
3152
|
+
const normalizedObjects = normalizeDocument(ast).objects;
|
|
3153
|
+
validateDocument({
|
|
3154
|
+
format: "worldorbit",
|
|
3155
|
+
version: "1.0",
|
|
3156
|
+
system: null,
|
|
3157
|
+
objects: normalizedObjects
|
|
3158
|
+
});
|
|
3159
|
+
const diagnostics = schemaVersion === "2.0-draft" && outputVersion === "2.0" ? [
|
|
3160
|
+
{
|
|
3161
|
+
code: "load.schema.deprecatedDraft",
|
|
3162
|
+
severity: "warning",
|
|
3163
|
+
source: "upgrade",
|
|
3164
|
+
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
3165
|
+
}
|
|
3166
|
+
] : [];
|
|
3167
|
+
return {
|
|
3168
|
+
format: "worldorbit",
|
|
3169
|
+
version: outputVersion,
|
|
3170
|
+
sourceVersion: "1.0",
|
|
3171
|
+
system,
|
|
3172
|
+
objects: normalizedObjects,
|
|
3173
|
+
diagnostics
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
function assertDraftSchemaHeader(tokens, line) {
|
|
3177
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || tokens[1].value.toLowerCase() !== "2.0-draft" && tokens[1].value.toLowerCase() !== "2.0") {
|
|
3178
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0" or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3179
|
+
}
|
|
3180
|
+
return tokens[1].value.toLowerCase() === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3181
|
+
}
|
|
3182
|
+
function startTopLevelSection(tokens, line, system, objectNodes, viewpointIds, annotationIds, flags) {
|
|
3183
|
+
const keyword = tokens[0]?.value.toLowerCase();
|
|
3184
|
+
switch (keyword) {
|
|
3185
|
+
case "system":
|
|
3186
|
+
if (system) {
|
|
3187
|
+
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
3188
|
+
}
|
|
3189
|
+
return startSystemSection(tokens, line);
|
|
3190
|
+
case "defaults":
|
|
3191
|
+
if (!system) {
|
|
3192
|
+
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
3193
|
+
}
|
|
3194
|
+
if (flags.sawDefaults) {
|
|
3195
|
+
throw new WorldOrbitError('Atlas section "defaults" may only appear once', line, tokens[0].column);
|
|
3196
|
+
}
|
|
3197
|
+
return {
|
|
3198
|
+
kind: "defaults",
|
|
3199
|
+
system,
|
|
3200
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3201
|
+
};
|
|
3202
|
+
case "atlas":
|
|
3203
|
+
if (!system) {
|
|
3204
|
+
throw new WorldOrbitError('Atlas section "atlas" requires a preceding system declaration', line, tokens[0].column);
|
|
3205
|
+
}
|
|
3206
|
+
if (flags.sawAtlas) {
|
|
3207
|
+
throw new WorldOrbitError('Atlas section "atlas" may only appear once', line, tokens[0].column);
|
|
3208
|
+
}
|
|
3209
|
+
return {
|
|
3210
|
+
kind: "atlas",
|
|
3211
|
+
system,
|
|
3212
|
+
inMetadata: false,
|
|
3213
|
+
metadataIndent: null
|
|
3214
|
+
};
|
|
3215
|
+
case "viewpoint":
|
|
3216
|
+
if (!system) {
|
|
3217
|
+
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3218
|
+
}
|
|
3219
|
+
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
3220
|
+
case "annotation":
|
|
3221
|
+
if (!system) {
|
|
3222
|
+
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
3223
|
+
}
|
|
3224
|
+
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
3225
|
+
case "object":
|
|
3226
|
+
return startObjectSection(tokens, line, objectNodes);
|
|
3227
|
+
default:
|
|
3228
|
+
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
function startSystemSection(tokens, line) {
|
|
3232
|
+
if (tokens.length !== 2) {
|
|
3233
|
+
throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
|
|
3234
|
+
}
|
|
3235
|
+
const system = {
|
|
3236
|
+
type: "system",
|
|
3237
|
+
id: tokens[1].value,
|
|
3238
|
+
title: null,
|
|
3239
|
+
defaults: {
|
|
3240
|
+
view: "topdown",
|
|
3241
|
+
scale: null,
|
|
3242
|
+
units: null,
|
|
3243
|
+
preset: null,
|
|
3244
|
+
theme: null
|
|
3245
|
+
},
|
|
3246
|
+
atlasMetadata: {},
|
|
3247
|
+
viewpoints: [],
|
|
3248
|
+
annotations: []
|
|
3249
|
+
};
|
|
3250
|
+
return {
|
|
3251
|
+
kind: "system",
|
|
3252
|
+
system,
|
|
3253
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
3257
|
+
if (tokens.length !== 2) {
|
|
3258
|
+
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3259
|
+
}
|
|
3260
|
+
const id = normalizeIdentifier2(tokens[1].value);
|
|
3261
|
+
if (!id) {
|
|
3262
|
+
throw new WorldOrbitError("Viewpoint id must not be empty", line, tokens[1].column);
|
|
3263
|
+
}
|
|
3264
|
+
if (viewpointIds.has(id)) {
|
|
3265
|
+
throw new WorldOrbitError(`Duplicate viewpoint id "${id}"`, line, tokens[1].column);
|
|
3266
|
+
}
|
|
3267
|
+
const viewpoint = {
|
|
3268
|
+
id,
|
|
3269
|
+
label: humanizeIdentifier3(id),
|
|
3270
|
+
summary: "",
|
|
3271
|
+
focusObjectId: null,
|
|
3272
|
+
selectedObjectId: null,
|
|
3273
|
+
projection: system.defaults.view,
|
|
3274
|
+
preset: system.defaults.preset,
|
|
3275
|
+
zoom: null,
|
|
3276
|
+
rotationDeg: 0,
|
|
3277
|
+
layers: {},
|
|
3278
|
+
filter: null
|
|
3279
|
+
};
|
|
3280
|
+
system.viewpoints.push(viewpoint);
|
|
3281
|
+
viewpointIds.add(id);
|
|
3282
|
+
return {
|
|
3283
|
+
kind: "viewpoint",
|
|
3284
|
+
viewpoint,
|
|
3285
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
3286
|
+
inFilter: false,
|
|
3287
|
+
filterIndent: null,
|
|
3288
|
+
seenFilterFields: /* @__PURE__ */ new Set()
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
3292
|
+
if (tokens.length !== 2) {
|
|
3293
|
+
throw new WorldOrbitError("Invalid annotation declaration", line, tokens[0]?.column ?? 1);
|
|
3294
|
+
}
|
|
3295
|
+
const id = normalizeIdentifier2(tokens[1].value);
|
|
3296
|
+
if (!id) {
|
|
3297
|
+
throw new WorldOrbitError("Annotation id must not be empty", line, tokens[1].column);
|
|
3298
|
+
}
|
|
3299
|
+
if (annotationIds.has(id)) {
|
|
3300
|
+
throw new WorldOrbitError(`Duplicate annotation id "${id}"`, line, tokens[1].column);
|
|
3301
|
+
}
|
|
3302
|
+
const annotation = {
|
|
3303
|
+
id,
|
|
3304
|
+
label: humanizeIdentifier3(id),
|
|
3305
|
+
targetObjectId: null,
|
|
3306
|
+
body: "",
|
|
3307
|
+
tags: [],
|
|
3308
|
+
sourceObjectId: null
|
|
3309
|
+
};
|
|
3310
|
+
system.annotations.push(annotation);
|
|
3311
|
+
annotationIds.add(id);
|
|
3312
|
+
return {
|
|
3313
|
+
kind: "annotation",
|
|
3314
|
+
annotation,
|
|
3315
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
function startObjectSection(tokens, line, objectNodes) {
|
|
3319
|
+
if (tokens.length < 3) {
|
|
3320
|
+
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
3321
|
+
}
|
|
3322
|
+
const objectTypeToken = tokens[1];
|
|
3323
|
+
const idToken = tokens[2];
|
|
3324
|
+
const objectType = objectTypeToken.value;
|
|
3325
|
+
if (!WORLDORBIT_OBJECT_TYPES.has(objectType) || objectType === "system") {
|
|
3326
|
+
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
3327
|
+
}
|
|
3328
|
+
const objectNode = {
|
|
3329
|
+
type: "object",
|
|
3330
|
+
objectType,
|
|
3331
|
+
name: idToken.value,
|
|
3332
|
+
inlineFields: parseInlineFields2(tokens.slice(3), line),
|
|
3333
|
+
blockFields: [],
|
|
3334
|
+
infoEntries: [],
|
|
3335
|
+
location: {
|
|
3336
|
+
line,
|
|
3337
|
+
column: objectTypeToken.column
|
|
3338
|
+
}
|
|
3339
|
+
};
|
|
3340
|
+
objectNodes.push(objectNode);
|
|
3341
|
+
return {
|
|
3342
|
+
kind: "object",
|
|
3343
|
+
objectNode,
|
|
3344
|
+
inInfoBlock: false,
|
|
3345
|
+
infoIndent: null
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
function handleSectionLine(section, indent, tokens, line) {
|
|
3349
|
+
switch (section.kind) {
|
|
3350
|
+
case "system":
|
|
3351
|
+
applySystemField(section, tokens, line);
|
|
3352
|
+
return;
|
|
3353
|
+
case "defaults":
|
|
3354
|
+
applyDefaultsField(section, tokens, line);
|
|
3355
|
+
return;
|
|
3356
|
+
case "atlas":
|
|
3357
|
+
applyAtlasField(section, indent, tokens, line);
|
|
3358
|
+
return;
|
|
3359
|
+
case "viewpoint":
|
|
3360
|
+
applyViewpointField2(section, indent, tokens, line);
|
|
3361
|
+
return;
|
|
3362
|
+
case "annotation":
|
|
3363
|
+
applyAnnotationField(section, tokens, line);
|
|
3364
|
+
return;
|
|
3365
|
+
case "object":
|
|
3366
|
+
applyObjectField(section, indent, tokens, line);
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
function applySystemField(section, tokens, line) {
|
|
3371
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3372
|
+
if (key !== "title") {
|
|
3373
|
+
throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3374
|
+
}
|
|
3375
|
+
section.system.title = joinFieldValue(tokens, line);
|
|
3376
|
+
}
|
|
3377
|
+
function applyDefaultsField(section, tokens, line) {
|
|
3378
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3379
|
+
const value = joinFieldValue(tokens, line);
|
|
3380
|
+
switch (key) {
|
|
3381
|
+
case "view":
|
|
3382
|
+
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
3383
|
+
return;
|
|
3384
|
+
case "scale":
|
|
3385
|
+
section.system.defaults.scale = value;
|
|
3386
|
+
return;
|
|
3387
|
+
case "units":
|
|
3388
|
+
section.system.defaults.units = value;
|
|
3389
|
+
return;
|
|
3390
|
+
case "preset":
|
|
3391
|
+
section.system.defaults.preset = parsePresetValue(value, line, tokens[0].column);
|
|
3392
|
+
return;
|
|
3393
|
+
case "theme":
|
|
3394
|
+
section.system.defaults.theme = value;
|
|
3395
|
+
return;
|
|
3396
|
+
default:
|
|
3397
|
+
throw new WorldOrbitError(`Unknown defaults field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
function applyAtlasField(section, indent, tokens, line) {
|
|
3401
|
+
if (section.inMetadata && indent <= (section.metadataIndent ?? 0)) {
|
|
3402
|
+
section.inMetadata = false;
|
|
3403
|
+
section.metadataIndent = null;
|
|
3404
|
+
}
|
|
3405
|
+
if (section.inMetadata) {
|
|
3406
|
+
if (tokens.length < 2) {
|
|
3407
|
+
throw new WorldOrbitError("Invalid atlas metadata entry", line, tokens[0]?.column ?? 1);
|
|
3408
|
+
}
|
|
3409
|
+
const key = tokens[0].value;
|
|
3410
|
+
if (key in section.system.atlasMetadata) {
|
|
3411
|
+
throw new WorldOrbitError(`Duplicate atlas metadata key "${key}"`, line, tokens[0].column);
|
|
3412
|
+
}
|
|
3413
|
+
section.system.atlasMetadata[key] = joinFieldValue(tokens, line);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
|
|
3417
|
+
section.inMetadata = true;
|
|
3418
|
+
section.metadataIndent = indent;
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3422
|
+
}
|
|
3423
|
+
function applyViewpointField2(section, indent, tokens, line) {
|
|
3424
|
+
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
3425
|
+
section.inFilter = false;
|
|
3426
|
+
section.filterIndent = null;
|
|
3427
|
+
}
|
|
3428
|
+
if (section.inFilter) {
|
|
3429
|
+
applyViewpointFilterField(section, tokens, line);
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
3433
|
+
if (section.seenFields.has("filter")) {
|
|
3434
|
+
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
3435
|
+
}
|
|
3436
|
+
section.seenFields.add("filter");
|
|
3437
|
+
section.inFilter = true;
|
|
3438
|
+
section.filterIndent = indent;
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3442
|
+
const value = joinFieldValue(tokens, line);
|
|
3443
|
+
switch (key) {
|
|
3444
|
+
case "label":
|
|
3445
|
+
section.viewpoint.label = value;
|
|
3446
|
+
return;
|
|
3447
|
+
case "summary":
|
|
3448
|
+
section.viewpoint.summary = value;
|
|
3449
|
+
return;
|
|
3450
|
+
case "focus":
|
|
3451
|
+
section.viewpoint.focusObjectId = value;
|
|
3452
|
+
return;
|
|
3453
|
+
case "select":
|
|
3454
|
+
section.viewpoint.selectedObjectId = value;
|
|
3455
|
+
return;
|
|
3456
|
+
case "projection":
|
|
3457
|
+
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
3458
|
+
return;
|
|
3459
|
+
case "preset":
|
|
3460
|
+
section.viewpoint.preset = parsePresetValue(value, line, tokens[0].column);
|
|
3461
|
+
return;
|
|
3462
|
+
case "zoom":
|
|
3463
|
+
section.viewpoint.zoom = parsePositiveNumber2(value, line, tokens[0].column, "zoom");
|
|
3464
|
+
return;
|
|
3465
|
+
case "rotation":
|
|
3466
|
+
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
3467
|
+
return;
|
|
3468
|
+
case "layers":
|
|
3469
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
3470
|
+
return;
|
|
3471
|
+
default:
|
|
3472
|
+
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function applyViewpointFilterField(section, tokens, line) {
|
|
3476
|
+
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
3477
|
+
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
|
|
3478
|
+
switch (key) {
|
|
3479
|
+
case "query":
|
|
3480
|
+
filter.query = joinFieldValue(tokens, line);
|
|
3481
|
+
break;
|
|
3482
|
+
case "objecttypes":
|
|
3483
|
+
filter.objectTypes = parseObjectTypeTokens(tokens.slice(1), line);
|
|
3484
|
+
break;
|
|
3485
|
+
case "tags":
|
|
3486
|
+
filter.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3487
|
+
break;
|
|
3488
|
+
case "groups":
|
|
3489
|
+
filter.groupIds = parseTokenList(tokens.slice(1), line, "groups");
|
|
3490
|
+
break;
|
|
3491
|
+
default:
|
|
3492
|
+
throw new WorldOrbitError(`Unknown viewpoint filter field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3493
|
+
}
|
|
3494
|
+
section.viewpoint.filter = filter;
|
|
3495
|
+
}
|
|
3496
|
+
function applyAnnotationField(section, tokens, line) {
|
|
3497
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3498
|
+
switch (key) {
|
|
3499
|
+
case "label":
|
|
3500
|
+
section.annotation.label = joinFieldValue(tokens, line);
|
|
3501
|
+
return;
|
|
3502
|
+
case "target":
|
|
3503
|
+
section.annotation.targetObjectId = joinFieldValue(tokens, line);
|
|
3504
|
+
return;
|
|
3505
|
+
case "body":
|
|
3506
|
+
section.annotation.body = joinFieldValue(tokens, line);
|
|
3507
|
+
return;
|
|
3508
|
+
case "tags":
|
|
3509
|
+
section.annotation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
3510
|
+
return;
|
|
3511
|
+
default:
|
|
3512
|
+
throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
function applyObjectField(section, indent, tokens, line) {
|
|
3516
|
+
if (tokens.length === 1 && tokens[0].value === "info") {
|
|
3517
|
+
section.inInfoBlock = true;
|
|
3518
|
+
section.infoIndent = indent;
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
if (section.inInfoBlock && indent <= (section.infoIndent ?? 0)) {
|
|
3522
|
+
section.inInfoBlock = false;
|
|
3523
|
+
section.infoIndent = null;
|
|
3524
|
+
}
|
|
3525
|
+
if (section.inInfoBlock) {
|
|
3526
|
+
section.objectNode.infoEntries.push(parseInfoEntry2(tokens, line));
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
section.objectNode.blockFields.push(parseField2(tokens, line));
|
|
3530
|
+
}
|
|
3531
|
+
function requireUniqueField(tokens, seenFields, line) {
|
|
3532
|
+
if (tokens.length < 2) {
|
|
3533
|
+
throw new WorldOrbitError("Invalid atlas field line", line, tokens[0]?.column ?? 1);
|
|
3534
|
+
}
|
|
3535
|
+
const key = tokens[0].value.toLowerCase();
|
|
3536
|
+
if (seenFields.has(key)) {
|
|
3537
|
+
throw new WorldOrbitError(`Duplicate atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3538
|
+
}
|
|
3539
|
+
seenFields.add(key);
|
|
3540
|
+
return key;
|
|
3541
|
+
}
|
|
3542
|
+
function joinFieldValue(tokens, line) {
|
|
3543
|
+
if (tokens.length < 2) {
|
|
3544
|
+
throw new WorldOrbitError("Missing value for atlas field", line, tokens[0]?.column ?? 1);
|
|
3545
|
+
}
|
|
3546
|
+
return tokens.slice(1).map((token) => token.value).join(" ").trim();
|
|
3547
|
+
}
|
|
3548
|
+
function parseObjectTypeTokens(tokens, line) {
|
|
3549
|
+
if (tokens.length === 0) {
|
|
3550
|
+
throw new WorldOrbitError("Missing value for atlas field", line);
|
|
3551
|
+
}
|
|
3552
|
+
return tokens.map((token) => {
|
|
3553
|
+
const value = token.value;
|
|
3554
|
+
if (value !== "star" && value !== "planet" && value !== "moon" && value !== "belt" && value !== "asteroid" && value !== "comet" && value !== "ring" && value !== "structure" && value !== "phenomenon") {
|
|
3555
|
+
throw new WorldOrbitError(`Unknown viewpoint object type "${token.value}"`, line, token.column);
|
|
3556
|
+
}
|
|
3557
|
+
return value;
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
function parseTokenList(tokens, line, field) {
|
|
3561
|
+
if (tokens.length === 0) {
|
|
3562
|
+
throw new WorldOrbitError(`Missing value for field "${field}"`, line);
|
|
3563
|
+
}
|
|
3564
|
+
return tokens.map((token) => token.value);
|
|
3565
|
+
}
|
|
3566
|
+
function parseLayerTokens(tokens, line) {
|
|
3567
|
+
if (tokens.length === 0) {
|
|
3568
|
+
throw new WorldOrbitError('Missing value for field "layers"', line);
|
|
3569
|
+
}
|
|
3570
|
+
const next = {};
|
|
3571
|
+
for (const token of tokens) {
|
|
3572
|
+
const enabled = !token.value.startsWith("-") && !token.value.startsWith("!");
|
|
3573
|
+
const rawLayer = token.value.replace(/^[-!]+/, "").toLowerCase();
|
|
3574
|
+
if (rawLayer === "orbits") {
|
|
3575
|
+
next["orbits-back"] = enabled;
|
|
3576
|
+
next["orbits-front"] = enabled;
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
3580
|
+
next[rawLayer] = enabled;
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
throw new WorldOrbitError(`Unknown layer token "${token.value}"`, line, token.column);
|
|
3584
|
+
}
|
|
3585
|
+
return next;
|
|
3586
|
+
}
|
|
3587
|
+
function parseProjectionValue(value, line, column) {
|
|
3588
|
+
const normalized = value.toLowerCase();
|
|
3589
|
+
if (normalized === "topdown" || normalized === "isometric") {
|
|
3590
|
+
return normalized;
|
|
3591
|
+
}
|
|
3592
|
+
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
3593
|
+
}
|
|
3594
|
+
function parsePresetValue(value, line, column) {
|
|
3595
|
+
const normalized = value.toLowerCase();
|
|
3596
|
+
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
3597
|
+
return normalized;
|
|
3598
|
+
}
|
|
3599
|
+
throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
|
|
3600
|
+
}
|
|
3601
|
+
function parsePositiveNumber2(value, line, column, field) {
|
|
3602
|
+
const parsed = Number(value);
|
|
3603
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3604
|
+
throw new WorldOrbitError(`Field "${field}" expects a positive number`, line, column);
|
|
3605
|
+
}
|
|
3606
|
+
return parsed;
|
|
3607
|
+
}
|
|
3608
|
+
function parseFiniteNumber2(value, line, column, field) {
|
|
3609
|
+
const parsed = Number(value);
|
|
3610
|
+
if (!Number.isFinite(parsed)) {
|
|
3611
|
+
throw new WorldOrbitError(`Field "${field}" expects a finite number`, line, column);
|
|
3612
|
+
}
|
|
3613
|
+
return parsed;
|
|
3614
|
+
}
|
|
3615
|
+
function createEmptyViewpointFilter2() {
|
|
3616
|
+
return {
|
|
3617
|
+
query: null,
|
|
3618
|
+
objectTypes: [],
|
|
3619
|
+
tags: [],
|
|
3620
|
+
groupIds: []
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
function parseInlineFields2(tokens, line) {
|
|
3624
|
+
const fields = [];
|
|
3625
|
+
let index = 0;
|
|
3626
|
+
while (index < tokens.length) {
|
|
3627
|
+
const keyToken = tokens[index];
|
|
3628
|
+
const schema = getFieldSchema(keyToken.value);
|
|
3629
|
+
if (!schema) {
|
|
3630
|
+
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
3631
|
+
}
|
|
3632
|
+
index++;
|
|
3633
|
+
const valueTokens = [];
|
|
3634
|
+
if (schema.arity === "multiple") {
|
|
3635
|
+
while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
|
|
3636
|
+
valueTokens.push(tokens[index]);
|
|
3637
|
+
index++;
|
|
3638
|
+
}
|
|
3639
|
+
} else {
|
|
3640
|
+
const nextToken = tokens[index];
|
|
3641
|
+
if (nextToken) {
|
|
3642
|
+
valueTokens.push(nextToken);
|
|
3643
|
+
index++;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
if (valueTokens.length === 0) {
|
|
3647
|
+
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
3648
|
+
}
|
|
3649
|
+
fields.push({
|
|
3650
|
+
type: "field",
|
|
3651
|
+
key: keyToken.value,
|
|
3652
|
+
values: valueTokens.map((token) => token.value),
|
|
3653
|
+
location: { line, column: keyToken.column }
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
return fields;
|
|
3657
|
+
}
|
|
3658
|
+
function parseField2(tokens, line) {
|
|
3659
|
+
if (tokens.length < 2) {
|
|
3660
|
+
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
3661
|
+
}
|
|
3662
|
+
if (!getFieldSchema(tokens[0].value)) {
|
|
3663
|
+
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3664
|
+
}
|
|
3665
|
+
return {
|
|
3666
|
+
type: "field",
|
|
3667
|
+
key: tokens[0].value,
|
|
3668
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
3669
|
+
location: { line, column: tokens[0].column }
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
function parseInfoEntry2(tokens, line) {
|
|
3673
|
+
if (tokens.length < 2) {
|
|
3674
|
+
throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
|
|
3675
|
+
}
|
|
3676
|
+
return {
|
|
3677
|
+
type: "info-entry",
|
|
3678
|
+
key: tokens[0].value,
|
|
3679
|
+
value: tokens.slice(1).map((token) => token.value).join(" "),
|
|
3680
|
+
location: { line, column: tokens[0].column }
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
function normalizeIdentifier2(value) {
|
|
3684
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3685
|
+
}
|
|
3686
|
+
function humanizeIdentifier3(value) {
|
|
3687
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// packages/core/dist/atlas-edit.js
|
|
3691
|
+
function createEmptyAtlasDocument(systemId = "WorldOrbit") {
|
|
3692
|
+
return {
|
|
3693
|
+
format: "worldorbit",
|
|
3694
|
+
version: "2.0",
|
|
3695
|
+
sourceVersion: "1.0",
|
|
3696
|
+
system: {
|
|
3697
|
+
type: "system",
|
|
3698
|
+
id: systemId,
|
|
3699
|
+
title: systemId,
|
|
3700
|
+
defaults: {
|
|
3701
|
+
view: "topdown",
|
|
3702
|
+
scale: null,
|
|
3703
|
+
units: null,
|
|
3704
|
+
preset: null,
|
|
3705
|
+
theme: null
|
|
3706
|
+
},
|
|
3707
|
+
atlasMetadata: {},
|
|
3708
|
+
viewpoints: [],
|
|
3709
|
+
annotations: []
|
|
3710
|
+
},
|
|
3711
|
+
objects: [],
|
|
3712
|
+
diagnostics: []
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3715
|
+
function cloneAtlasDocument(document) {
|
|
3716
|
+
return structuredClone(document);
|
|
3717
|
+
}
|
|
3718
|
+
function listAtlasDocumentPaths(document) {
|
|
3719
|
+
const paths = [{ kind: "system" }, { kind: "defaults" }];
|
|
3720
|
+
if (document.system) {
|
|
3721
|
+
for (const key of Object.keys(document.system.atlasMetadata).sort()) {
|
|
3722
|
+
paths.push({ kind: "metadata", key });
|
|
3723
|
+
}
|
|
3724
|
+
for (const viewpoint of [...document.system.viewpoints].sort(compareIdLike)) {
|
|
3725
|
+
paths.push({ kind: "viewpoint", id: viewpoint.id });
|
|
3726
|
+
}
|
|
3727
|
+
for (const annotation of [...document.system.annotations].sort(compareIdLike)) {
|
|
3728
|
+
paths.push({ kind: "annotation", id: annotation.id });
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
for (const object of [...document.objects].sort(compareIdLike)) {
|
|
3732
|
+
paths.push({ kind: "object", id: object.id });
|
|
3733
|
+
}
|
|
3734
|
+
return paths;
|
|
3735
|
+
}
|
|
3736
|
+
function getAtlasDocumentNode(document, path) {
|
|
3737
|
+
switch (path.kind) {
|
|
3738
|
+
case "system":
|
|
3739
|
+
return document.system;
|
|
3740
|
+
case "defaults":
|
|
3741
|
+
return document.system?.defaults ?? null;
|
|
3742
|
+
case "metadata":
|
|
3743
|
+
return path.key ? document.system?.atlasMetadata[path.key] ?? null : null;
|
|
3744
|
+
case "object":
|
|
3745
|
+
return path.id ? findObject(document, path.id) : null;
|
|
3746
|
+
case "viewpoint":
|
|
3747
|
+
return path.id ? findViewpoint(document.system, path.id) : null;
|
|
3748
|
+
case "annotation":
|
|
3749
|
+
return path.id ? findAnnotation(document.system, path.id) : null;
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
function upsertAtlasDocumentNode(document, path, value) {
|
|
3753
|
+
const next = cloneAtlasDocument(document);
|
|
3754
|
+
const system = ensureSystem(next);
|
|
3755
|
+
switch (path.kind) {
|
|
3756
|
+
case "system":
|
|
3757
|
+
next.system = value;
|
|
3758
|
+
return next;
|
|
3759
|
+
case "defaults":
|
|
3760
|
+
system.defaults = {
|
|
3761
|
+
...system.defaults,
|
|
3762
|
+
...value
|
|
3763
|
+
};
|
|
3764
|
+
return next;
|
|
3765
|
+
case "metadata":
|
|
3766
|
+
if (!path.key) {
|
|
3767
|
+
throw new Error('Metadata updates require a "key" value.');
|
|
3768
|
+
}
|
|
3769
|
+
if (value === null || value === void 0 || value === "") {
|
|
3770
|
+
delete system.atlasMetadata[path.key];
|
|
3771
|
+
} else {
|
|
3772
|
+
system.atlasMetadata[path.key] = String(value);
|
|
3773
|
+
}
|
|
3774
|
+
return next;
|
|
3775
|
+
case "object":
|
|
3776
|
+
if (!path.id) {
|
|
3777
|
+
throw new Error('Object updates require an "id" value.');
|
|
3778
|
+
}
|
|
3779
|
+
upsertById(next.objects, value);
|
|
3780
|
+
return next;
|
|
3781
|
+
case "viewpoint":
|
|
3782
|
+
if (!path.id) {
|
|
3783
|
+
throw new Error('Viewpoint updates require an "id" value.');
|
|
3784
|
+
}
|
|
3785
|
+
upsertById(system.viewpoints, value);
|
|
3786
|
+
return next;
|
|
3787
|
+
case "annotation":
|
|
3788
|
+
if (!path.id) {
|
|
3789
|
+
throw new Error('Annotation updates require an "id" value.');
|
|
3790
|
+
}
|
|
3791
|
+
upsertById(system.annotations, value);
|
|
3792
|
+
return next;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
function updateAtlasDocumentNode(document, path, updater) {
|
|
3796
|
+
return upsertAtlasDocumentNode(document, path, updater(getAtlasDocumentNode(document, path)));
|
|
3797
|
+
}
|
|
3798
|
+
function removeAtlasDocumentNode(document, path) {
|
|
3799
|
+
const next = cloneAtlasDocument(document);
|
|
3800
|
+
const system = ensureSystem(next);
|
|
3801
|
+
switch (path.kind) {
|
|
3802
|
+
case "metadata":
|
|
3803
|
+
if (path.key) {
|
|
3804
|
+
delete system.atlasMetadata[path.key];
|
|
3805
|
+
}
|
|
3806
|
+
return next;
|
|
3807
|
+
case "object":
|
|
3808
|
+
if (path.id) {
|
|
3809
|
+
next.objects = next.objects.filter((object) => object.id !== path.id);
|
|
3810
|
+
}
|
|
3811
|
+
return next;
|
|
3812
|
+
case "viewpoint":
|
|
3813
|
+
if (path.id) {
|
|
3814
|
+
system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
|
|
3815
|
+
}
|
|
3816
|
+
return next;
|
|
3817
|
+
case "annotation":
|
|
3818
|
+
if (path.id) {
|
|
3819
|
+
system.annotations = system.annotations.filter((annotation) => annotation.id !== path.id);
|
|
3820
|
+
}
|
|
3821
|
+
return next;
|
|
3822
|
+
default:
|
|
3823
|
+
return next;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
function resolveAtlasDiagnostics(document, diagnostics) {
|
|
3827
|
+
return diagnostics.map((diagnostic) => ({
|
|
3828
|
+
diagnostic,
|
|
3829
|
+
path: resolveAtlasDiagnosticPath(document, diagnostic)
|
|
3830
|
+
}));
|
|
3831
|
+
}
|
|
3832
|
+
function resolveAtlasDiagnosticPath(document, diagnostic) {
|
|
3833
|
+
if (diagnostic.objectId && findObject(document, diagnostic.objectId)) {
|
|
3834
|
+
return {
|
|
3835
|
+
kind: "object",
|
|
3836
|
+
id: diagnostic.objectId
|
|
3837
|
+
};
|
|
3838
|
+
}
|
|
3839
|
+
if (diagnostic.field?.startsWith("viewpoint.")) {
|
|
3840
|
+
const parts = diagnostic.field.split(".");
|
|
3841
|
+
if (parts[1] && findViewpoint(document.system, parts[1])) {
|
|
3842
|
+
return {
|
|
3843
|
+
kind: "viewpoint",
|
|
3844
|
+
id: parts[1]
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
if (diagnostic.field?.startsWith("annotation.")) {
|
|
3849
|
+
const parts = diagnostic.field.split(".");
|
|
3850
|
+
if (parts[1] && findAnnotation(document.system, parts[1])) {
|
|
3851
|
+
return {
|
|
3852
|
+
kind: "annotation",
|
|
3853
|
+
id: parts[1]
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
if (diagnostic.field && diagnostic.field in ensureSystem(document).atlasMetadata) {
|
|
3858
|
+
return {
|
|
3859
|
+
kind: "metadata",
|
|
3860
|
+
key: diagnostic.field
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
return null;
|
|
3864
|
+
}
|
|
3865
|
+
function validateAtlasDocumentWithDiagnostics(document) {
|
|
3866
|
+
const materialized = materializeAtlasDocument(document);
|
|
3867
|
+
const result = validateDocumentWithDiagnostics(materialized);
|
|
3868
|
+
return resolveAtlasDiagnostics(document, result.diagnostics);
|
|
3869
|
+
}
|
|
3870
|
+
function ensureSystem(document) {
|
|
3871
|
+
if (document.system) {
|
|
3872
|
+
return document.system;
|
|
3873
|
+
}
|
|
3874
|
+
document.system = createEmptyAtlasDocument().system;
|
|
3875
|
+
return document.system;
|
|
3876
|
+
}
|
|
3877
|
+
function findObject(document, objectId) {
|
|
3878
|
+
return document.objects.find((object) => object.id === objectId) ?? null;
|
|
3879
|
+
}
|
|
3880
|
+
function findViewpoint(system, viewpointId) {
|
|
3881
|
+
return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
|
|
3882
|
+
}
|
|
3883
|
+
function findAnnotation(system, annotationId) {
|
|
3884
|
+
return system?.annotations.find((annotation) => annotation.id === annotationId) ?? null;
|
|
3885
|
+
}
|
|
3886
|
+
function upsertById(items, value) {
|
|
3887
|
+
const index = items.findIndex((item) => item.id === value.id);
|
|
3888
|
+
if (index === -1) {
|
|
3889
|
+
items.push(value);
|
|
3890
|
+
items.sort(compareIdLike);
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3893
|
+
items[index] = value;
|
|
3894
|
+
}
|
|
3895
|
+
function compareIdLike(left, right) {
|
|
3896
|
+
return left.id.localeCompare(right.id);
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
// packages/core/dist/load.js
|
|
3900
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0)?$/i;
|
|
3901
|
+
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
3902
|
+
function detectWorldOrbitSchemaVersion(source) {
|
|
3903
|
+
for (const line of source.split(/\r?\n/)) {
|
|
3904
|
+
const trimmed = line.trim();
|
|
3905
|
+
if (!trimmed) {
|
|
3906
|
+
continue;
|
|
3907
|
+
}
|
|
3908
|
+
if (LEGACY_DRAFT_SCHEMA_PATTERN.test(trimmed)) {
|
|
3909
|
+
return "2.0-draft";
|
|
3910
|
+
}
|
|
3911
|
+
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
3912
|
+
return "2.0";
|
|
3913
|
+
}
|
|
3914
|
+
return "1.0";
|
|
3915
|
+
}
|
|
3916
|
+
return "1.0";
|
|
3917
|
+
}
|
|
3918
|
+
function loadWorldOrbitSource(source) {
|
|
3919
|
+
const result = loadWorldOrbitSourceWithDiagnostics(source);
|
|
3920
|
+
if (!result.ok || !result.value) {
|
|
3921
|
+
const diagnostic = result.diagnostics[0];
|
|
3922
|
+
throw new WorldOrbitError(diagnostic?.message ?? "Failed to load WorldOrbit source", diagnostic?.line, diagnostic?.column);
|
|
3923
|
+
}
|
|
3924
|
+
return result.value;
|
|
3925
|
+
}
|
|
3926
|
+
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
3927
|
+
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
3928
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft") {
|
|
3929
|
+
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
3930
|
+
}
|
|
3931
|
+
let ast;
|
|
3932
|
+
try {
|
|
3933
|
+
ast = parseWorldOrbit(source);
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
return {
|
|
3936
|
+
ok: false,
|
|
3937
|
+
value: null,
|
|
3938
|
+
diagnostics: [diagnosticFromError(error, "parse")]
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
let document;
|
|
3942
|
+
try {
|
|
3943
|
+
document = normalizeDocument(ast);
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
return {
|
|
3946
|
+
ok: false,
|
|
3947
|
+
value: null,
|
|
3948
|
+
diagnostics: [diagnosticFromError(error, "normalize")]
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
try {
|
|
3952
|
+
validateDocument(document);
|
|
3953
|
+
} catch (error) {
|
|
3954
|
+
return {
|
|
3955
|
+
ok: false,
|
|
3956
|
+
value: null,
|
|
3957
|
+
diagnostics: [diagnosticFromError(error, "validate")]
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
return {
|
|
3961
|
+
ok: true,
|
|
3962
|
+
value: {
|
|
3963
|
+
schemaVersion,
|
|
3964
|
+
ast,
|
|
3965
|
+
document,
|
|
3966
|
+
atlasDocument: null,
|
|
3967
|
+
draftDocument: null,
|
|
3968
|
+
diagnostics: []
|
|
3969
|
+
},
|
|
3970
|
+
diagnostics: []
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
function loadAtlasSourceWithDiagnostics(source, schemaVersion) {
|
|
3974
|
+
let atlasDocument;
|
|
3975
|
+
try {
|
|
3976
|
+
atlasDocument = parseWorldOrbitAtlas(source);
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
return {
|
|
3979
|
+
ok: false,
|
|
3980
|
+
value: null,
|
|
3981
|
+
diagnostics: [diagnosticFromError(error, "parse", "load.atlas.failed")]
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
let document;
|
|
3985
|
+
try {
|
|
3986
|
+
document = materializeAtlasDocument(atlasDocument);
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
return {
|
|
3989
|
+
ok: false,
|
|
3990
|
+
value: null,
|
|
3991
|
+
diagnostics: [diagnosticFromError(error, "normalize", "load.atlas.materialize.failed")]
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
try {
|
|
3995
|
+
validateDocument(document);
|
|
3996
|
+
} catch (error) {
|
|
3997
|
+
return {
|
|
3998
|
+
ok: false,
|
|
3999
|
+
value: null,
|
|
4000
|
+
diagnostics: [diagnosticFromError(error, "validate", "load.atlas.validate.failed")]
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
const loaded = {
|
|
4004
|
+
schemaVersion,
|
|
4005
|
+
ast: null,
|
|
4006
|
+
document,
|
|
4007
|
+
atlasDocument,
|
|
4008
|
+
draftDocument: atlasDocument,
|
|
4009
|
+
diagnostics: [...atlasDocument.diagnostics]
|
|
4010
|
+
};
|
|
4011
|
+
return {
|
|
4012
|
+
ok: true,
|
|
4013
|
+
value: loaded,
|
|
4014
|
+
diagnostics: [...atlasDocument.diagnostics]
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
// packages/core/dist/markdown.js
|
|
4019
|
+
var FENCE_PATTERN = /^```worldorbit(?:\s+(.*))?\s*$/;
|
|
4020
|
+
function extractWorldOrbitBlocks(markdown) {
|
|
4021
|
+
const lines = markdown.split(/\r?\n/);
|
|
4022
|
+
const blocks = [];
|
|
4023
|
+
let active = false;
|
|
4024
|
+
let activeInfo = null;
|
|
4025
|
+
let activeStartLine = 0;
|
|
4026
|
+
let buffer = [];
|
|
4027
|
+
lines.forEach((line, index) => {
|
|
4028
|
+
const lineNumber = index + 1;
|
|
4029
|
+
if (!active) {
|
|
4030
|
+
const match = line.match(FENCE_PATTERN);
|
|
4031
|
+
if (match) {
|
|
4032
|
+
active = true;
|
|
4033
|
+
activeInfo = match[1] ?? null;
|
|
4034
|
+
activeStartLine = lineNumber;
|
|
4035
|
+
buffer = [];
|
|
4036
|
+
}
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
if (line.trim() === "```") {
|
|
4040
|
+
blocks.push({
|
|
4041
|
+
source: buffer.join("\n"),
|
|
4042
|
+
info: activeInfo,
|
|
4043
|
+
startLine: activeStartLine,
|
|
4044
|
+
endLine: lineNumber
|
|
4045
|
+
});
|
|
4046
|
+
active = false;
|
|
4047
|
+
activeInfo = null;
|
|
4048
|
+
activeStartLine = 0;
|
|
4049
|
+
buffer = [];
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
buffer.push(line);
|
|
4053
|
+
});
|
|
4054
|
+
return blocks;
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
// packages/core/dist/index.js
|
|
4058
|
+
function parse(source) {
|
|
4059
|
+
const ast = parseWorldOrbit(source);
|
|
4060
|
+
const document = normalizeDocument(ast);
|
|
4061
|
+
validateDocument(document);
|
|
4062
|
+
return { ast, document };
|
|
4063
|
+
}
|
|
4064
|
+
function render(source) {
|
|
4065
|
+
const result = parse(source);
|
|
4066
|
+
return {
|
|
4067
|
+
...result,
|
|
4068
|
+
scene: renderDocumentToScene(result.document)
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
function load(source) {
|
|
4072
|
+
return loadWorldOrbitSource(source);
|
|
4073
|
+
}
|
|
4074
|
+
function parseSafe(source) {
|
|
4075
|
+
return parseWithDiagnostics(source);
|
|
4076
|
+
}
|
|
4077
|
+
function stringify(document, options = {}) {
|
|
4078
|
+
return formatDocument(document, options);
|
|
4079
|
+
}
|
|
4080
|
+
return __toCommonJS(dist_exports);
|
|
4081
|
+
})();
|