worldorbit 3.2.1 → 3.2.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.
Files changed (81) hide show
  1. package/dist/browser/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  2. package/dist/browser/obsidian-plugin/dist/diagnostics.js +23 -0
  3. package/dist/browser/obsidian-plugin/dist/examples.d.ts +3 -0
  4. package/dist/browser/obsidian-plugin/dist/examples.js +77 -0
  5. package/dist/browser/obsidian-plugin/dist/index.d.ts +9 -0
  6. package/dist/browser/obsidian-plugin/dist/index.js +8 -0
  7. package/dist/browser/obsidian-plugin/dist/main.d.ts +2 -0
  8. package/dist/browser/obsidian-plugin/dist/main.js +2 -0
  9. package/dist/browser/obsidian-plugin/dist/navigation.d.ts +6 -0
  10. package/dist/browser/obsidian-plugin/dist/navigation.js +44 -0
  11. package/dist/browser/obsidian-plugin/dist/plugin.d.ts +8 -0
  12. package/dist/browser/obsidian-plugin/dist/plugin.js +508 -0
  13. package/dist/browser/obsidian-plugin/dist/positions.d.ts +7 -0
  14. package/dist/browser/obsidian-plugin/dist/positions.js +14 -0
  15. package/dist/browser/obsidian-plugin/dist/settings.d.ts +2 -0
  16. package/dist/browser/obsidian-plugin/dist/settings.js +5 -0
  17. package/dist/browser/obsidian-plugin/dist/theme.d.ts +2 -0
  18. package/dist/browser/obsidian-plugin/dist/theme.js +31 -0
  19. package/dist/browser/obsidian-plugin/dist/types.d.ts +42 -0
  20. package/dist/browser/obsidian-plugin/dist/types.js +1 -0
  21. package/dist/browser/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  22. package/dist/browser/obsidian-plugin/dist/viewer-host.js +110 -0
  23. package/dist/browser/viewer/dist/index.d.ts +1 -0
  24. package/dist/browser/viewer/dist/index.js +1 -0
  25. package/dist/browser/viewer/dist/interactive-2d.d.ts +21 -0
  26. package/dist/browser/viewer/dist/interactive-2d.js +201 -0
  27. package/dist/browser/viewer/dist/render.d.ts +1 -1
  28. package/dist/browser/viewer/dist/render.js +2 -1
  29. package/dist/browser/viewer/dist/viewer-state.d.ts +1 -1
  30. package/dist/browser/viewer/dist/viewer-state.js +1 -1
  31. package/dist/obsidian-plugin/LICENSE +21 -0
  32. package/dist/obsidian-plugin/README.md +124 -0
  33. package/dist/obsidian-plugin/main.js +108 -0
  34. package/dist/obsidian-plugin/manifest.json +9 -0
  35. package/dist/obsidian-plugin/obsidian_scsh_1.png +0 -0
  36. package/dist/obsidian-plugin/obsidian_scsh_2.png +0 -0
  37. package/dist/obsidian-plugin/styles.css +164 -0
  38. package/dist/unpkg/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  39. package/dist/unpkg/obsidian-plugin/dist/diagnostics.js +23 -0
  40. package/dist/unpkg/obsidian-plugin/dist/examples.d.ts +3 -0
  41. package/dist/unpkg/obsidian-plugin/dist/examples.js +77 -0
  42. package/dist/unpkg/obsidian-plugin/dist/index.d.ts +9 -0
  43. package/dist/unpkg/obsidian-plugin/dist/index.js +8 -0
  44. package/dist/unpkg/obsidian-plugin/dist/main.d.ts +2 -0
  45. package/dist/unpkg/obsidian-plugin/dist/main.js +2 -0
  46. package/dist/unpkg/obsidian-plugin/dist/navigation.d.ts +6 -0
  47. package/dist/unpkg/obsidian-plugin/dist/navigation.js +44 -0
  48. package/dist/unpkg/obsidian-plugin/dist/plugin.d.ts +8 -0
  49. package/dist/unpkg/obsidian-plugin/dist/plugin.js +508 -0
  50. package/dist/unpkg/obsidian-plugin/dist/positions.d.ts +7 -0
  51. package/dist/unpkg/obsidian-plugin/dist/positions.js +14 -0
  52. package/dist/unpkg/obsidian-plugin/dist/settings.d.ts +2 -0
  53. package/dist/unpkg/obsidian-plugin/dist/settings.js +5 -0
  54. package/dist/unpkg/obsidian-plugin/dist/theme.d.ts +2 -0
  55. package/dist/unpkg/obsidian-plugin/dist/theme.js +31 -0
  56. package/dist/unpkg/obsidian-plugin/dist/types.d.ts +42 -0
  57. package/dist/unpkg/obsidian-plugin/dist/types.js +1 -0
  58. package/dist/unpkg/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  59. package/dist/unpkg/obsidian-plugin/dist/viewer-host.js +110 -0
  60. package/dist/unpkg/viewer/dist/index.d.ts +1 -0
  61. package/dist/unpkg/viewer/dist/index.js +1 -0
  62. package/dist/unpkg/viewer/dist/interactive-2d.d.ts +21 -0
  63. package/dist/unpkg/viewer/dist/interactive-2d.js +201 -0
  64. package/dist/unpkg/viewer/dist/render.d.ts +1 -1
  65. package/dist/unpkg/viewer/dist/render.js +2 -1
  66. package/dist/unpkg/viewer/dist/viewer-state.d.ts +1 -1
  67. package/dist/unpkg/viewer/dist/viewer-state.js +1 -1
  68. package/dist/unpkg/worldorbit-editor.min.js +56 -56
  69. package/dist/unpkg/worldorbit-markdown.min.js +15 -15
  70. package/dist/unpkg/worldorbit-viewer.min.js +207 -207
  71. package/dist/unpkg/worldorbit.js +200 -0
  72. package/dist/unpkg/worldorbit.min.js +210 -210
  73. package/package.json +18 -1
  74. package/packages/viewer/dist/index.d.ts +1 -0
  75. package/packages/viewer/dist/index.js +1 -0
  76. package/packages/viewer/dist/interactive-2d.d.ts +21 -0
  77. package/packages/viewer/dist/interactive-2d.js +201 -0
  78. package/packages/viewer/dist/render.d.ts +1 -1
  79. package/packages/viewer/dist/render.js +2 -1
  80. package/packages/viewer/dist/viewer-state.d.ts +1 -1
  81. package/packages/viewer/dist/viewer-state.js +1 -1
@@ -0,0 +1,110 @@
1
+ import { createInteractiveViewer2D, renderSceneToSvg, } from "@worldorbit/viewer/interactive-2d";
2
+ export class WorldOrbitEmbeddedView {
3
+ options;
4
+ viewer = null;
5
+ state;
6
+ constructor(options) {
7
+ this.options = {
8
+ ...options,
9
+ createViewer: options.createViewer ?? defaultCreateViewer,
10
+ renderStatic: options.renderStatic ?? defaultRenderStatic,
11
+ };
12
+ this.state = {
13
+ interactive: options.interactive,
14
+ destroyed: false,
15
+ };
16
+ }
17
+ getState() {
18
+ return { ...this.state };
19
+ }
20
+ mount() {
21
+ if (this.state.destroyed) {
22
+ return;
23
+ }
24
+ this.renderCurrent();
25
+ }
26
+ setInteractive(interactive) {
27
+ if (this.state.destroyed || this.state.interactive === interactive) {
28
+ return;
29
+ }
30
+ this.state.interactive = interactive;
31
+ this.renderCurrent();
32
+ }
33
+ resize() {
34
+ if (this.state.destroyed) {
35
+ return;
36
+ }
37
+ if (this.viewer) {
38
+ const viewport = measureViewport(this.options.container, this.options.scene);
39
+ this.viewer.setRenderOptions(viewport);
40
+ return;
41
+ }
42
+ this.renderCurrent();
43
+ }
44
+ destroy() {
45
+ if (this.state.destroyed) {
46
+ return;
47
+ }
48
+ this.state.destroyed = true;
49
+ this.destroyViewer();
50
+ clearElement(this.options.container);
51
+ }
52
+ renderCurrent() {
53
+ this.destroyViewer();
54
+ clearElement(this.options.container);
55
+ if (this.state.interactive) {
56
+ const viewport = measureViewport(this.options.container, this.options.scene);
57
+ this.viewer = this.options.createViewer(this.options.container, {
58
+ scene: this.options.scene,
59
+ theme: this.options.theme,
60
+ pointer: this.options.enablePointer,
61
+ touch: this.options.enableTouch,
62
+ ...viewport,
63
+ });
64
+ return;
65
+ }
66
+ const viewport = measureViewport(this.options.container, this.options.scene);
67
+ this.options.container.innerHTML = this.options.renderStatic(this.options.scene, {
68
+ theme: this.options.theme,
69
+ ...viewport,
70
+ });
71
+ }
72
+ destroyViewer() {
73
+ this.viewer?.destroy();
74
+ this.viewer = null;
75
+ }
76
+ }
77
+ function defaultCreateViewer(container, options) {
78
+ return createInteractiveViewer2D(container, options.scene, {
79
+ theme: options.theme,
80
+ pointer: options.pointer,
81
+ touch: options.touch,
82
+ selection: true,
83
+ width: options.width,
84
+ height: options.height,
85
+ });
86
+ }
87
+ function defaultRenderStatic(scene, options) {
88
+ return renderSceneToSvg(scene, {
89
+ theme: options.theme,
90
+ width: options.width,
91
+ height: options.height,
92
+ });
93
+ }
94
+ function measureViewport(container, scene) {
95
+ const rect = container.getBoundingClientRect();
96
+ const width = sanitizeDimension(container.clientWidth || rect.width) ?? scene.width;
97
+ const height = sanitizeDimension(container.clientHeight || rect.height) ??
98
+ Math.max(Math.round(width * (scene.height / Math.max(scene.width, 1))), 280);
99
+ return { width, height };
100
+ }
101
+ function sanitizeDimension(value) {
102
+ return Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
103
+ }
104
+ function clearElement(element) {
105
+ if (typeof element.empty === "function") {
106
+ element.empty();
107
+ return;
108
+ }
109
+ element.replaceChildren();
110
+ }
@@ -8,3 +8,4 @@ export { createEmbedPayload, createWorldOrbitEmbedMarkup, deserializeWorldOrbitE
8
8
  export { defineWorldOrbitViewerElement } from "./custom-element.js";
9
9
  export { createAtlasViewer } from "./atlas-viewer.js";
10
10
  export { createInteractiveViewer } from "./viewer.js";
11
+ export { createInteractiveViewer2D } from "./interactive-2d.js";
@@ -7,3 +7,4 @@ export { createEmbedPayload, createWorldOrbitEmbedMarkup, deserializeWorldOrbitE
7
7
  export { defineWorldOrbitViewerElement } from "./custom-element.js";
8
8
  export { createAtlasViewer } from "./atlas-viewer.js";
9
9
  export { createInteractiveViewer } from "./viewer.js";
10
+ export { createInteractiveViewer2D } from "./interactive-2d.js";
@@ -0,0 +1,21 @@
1
+ import type { RenderScene } from "@worldorbit/core/types";
2
+ import type { SvgRenderOptions, ViewerState } from "./types.js";
3
+ export interface InteractiveViewer2DOptions extends Pick<SvgRenderOptions, "width" | "height" | "padding" | "preset" | "theme" | "layers" | "subtitle"> {
4
+ pointer?: boolean;
5
+ touch?: boolean;
6
+ selection?: boolean;
7
+ minScale?: number;
8
+ maxScale?: number;
9
+ fitPadding?: number;
10
+ }
11
+ export interface WorldOrbitViewer2D {
12
+ getState(): ViewerState;
13
+ setState(state: Partial<ViewerState>): void;
14
+ setRenderOptions(options: Partial<InteractiveViewer2DOptions>): void;
15
+ fitToSystem(): void;
16
+ destroy(): void;
17
+ }
18
+ export { renderSceneToSvg } from "./render.js";
19
+ export { resolveTheme } from "./theme.js";
20
+ export type { WorldOrbitTheme } from "./types.js";
21
+ export declare function createInteractiveViewer2D(container: HTMLElement, scene: RenderScene, options?: InteractiveViewer2DOptions): WorldOrbitViewer2D;
@@ -0,0 +1,201 @@
1
+ import { renderSceneToSvg, WORLD_LAYER_ID } from "./render.js";
2
+ import { DEFAULT_VIEWER_STATE, composeViewerTransform, fitViewerState, panViewerState, zoomViewerStateAt, } from "./viewer-state.js";
3
+ export { renderSceneToSvg } from "./render.js";
4
+ export { resolveTheme } from "./theme.js";
5
+ const DEFAULT_VIEWER_LIMITS = {
6
+ minScale: 0.2,
7
+ maxScale: 8,
8
+ fitPadding: 48,
9
+ };
10
+ export function createInteractiveViewer2D(container, scene, options = {}) {
11
+ const constraints = {
12
+ minScale: options.minScale ?? DEFAULT_VIEWER_LIMITS.minScale,
13
+ maxScale: options.maxScale ?? DEFAULT_VIEWER_LIMITS.maxScale,
14
+ fitPadding: options.fitPadding ?? DEFAULT_VIEWER_LIMITS.fitPadding,
15
+ };
16
+ const behavior = {
17
+ pointer: options.pointer ?? true,
18
+ touch: options.touch ?? true,
19
+ selection: options.selection ?? true,
20
+ };
21
+ let renderOptions = {
22
+ width: options.width,
23
+ height: options.height,
24
+ padding: options.padding,
25
+ preset: options.preset,
26
+ theme: options.theme,
27
+ layers: options.layers,
28
+ subtitle: options.subtitle,
29
+ pointer: behavior.pointer,
30
+ touch: behavior.touch,
31
+ selection: behavior.selection,
32
+ minScale: constraints.minScale,
33
+ maxScale: constraints.maxScale,
34
+ fitPadding: constraints.fitPadding,
35
+ };
36
+ let state = fitViewerState(scene, DEFAULT_VIEWER_STATE, constraints);
37
+ let svgElement = null;
38
+ let cameraRoot = null;
39
+ let destroyed = false;
40
+ let activePointerId = null;
41
+ let lastPointerClientPoint = null;
42
+ let dragDistance = 0;
43
+ const previousTabIndex = container.getAttribute("tabindex");
44
+ const previousTouchAction = container.style.touchAction;
45
+ if (previousTabIndex === null) {
46
+ container.tabIndex = 0;
47
+ }
48
+ container.classList.add("wo-viewer-container");
49
+ container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
50
+ const handleWheel = (event) => {
51
+ if (!behavior.pointer || destroyed || !svgElement) {
52
+ return;
53
+ }
54
+ event.preventDefault();
55
+ container.focus();
56
+ const anchor = getScenePointFromClient(event.clientX, event.clientY);
57
+ const factor = clamp(Math.exp(-event.deltaY * 0.002), 0.6, 1.6);
58
+ updateState(zoomViewerStateAt(scene, state, factor, anchor, constraints));
59
+ };
60
+ const handlePointerDown = (event) => {
61
+ if (destroyed) {
62
+ return;
63
+ }
64
+ const isTouch = event.pointerType === "touch";
65
+ if ((isTouch && !behavior.touch) || (!isTouch && !behavior.pointer)) {
66
+ return;
67
+ }
68
+ if (!isTouch && event.button !== 0) {
69
+ return;
70
+ }
71
+ activePointerId = event.pointerId;
72
+ lastPointerClientPoint = { x: event.clientX, y: event.clientY };
73
+ dragDistance = 0;
74
+ container.setPointerCapture?.(event.pointerId);
75
+ container.focus();
76
+ };
77
+ const handlePointerMove = (event) => {
78
+ if (destroyed || activePointerId !== event.pointerId || !lastPointerClientPoint) {
79
+ return;
80
+ }
81
+ const rect = svgElement?.getBoundingClientRect();
82
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
83
+ return;
84
+ }
85
+ const dx = event.clientX - lastPointerClientPoint.x;
86
+ const dy = event.clientY - lastPointerClientPoint.y;
87
+ lastPointerClientPoint = { x: event.clientX, y: event.clientY };
88
+ dragDistance += Math.hypot(dx, dy);
89
+ updateState(panViewerState(state, dx * (scene.width / rect.width), dy * (scene.height / rect.height)));
90
+ };
91
+ const stopPointer = (event) => {
92
+ if (activePointerId !== event.pointerId) {
93
+ return;
94
+ }
95
+ activePointerId = null;
96
+ lastPointerClientPoint = null;
97
+ container.releasePointerCapture?.(event.pointerId);
98
+ };
99
+ const handleClick = (event) => {
100
+ if (destroyed || !behavior.selection || dragDistance > 6) {
101
+ return;
102
+ }
103
+ const objectEl = event.target?.closest(".wo-object[data-object-id]");
104
+ if (!objectEl) {
105
+ return;
106
+ }
107
+ updateState({
108
+ ...state,
109
+ selectedObjectId: objectEl.dataset.objectId ?? null,
110
+ });
111
+ renderSvg();
112
+ };
113
+ container.addEventListener("wheel", handleWheel, { passive: false });
114
+ container.addEventListener("pointerdown", handlePointerDown);
115
+ container.addEventListener("pointermove", handlePointerMove);
116
+ container.addEventListener("pointerup", stopPointer);
117
+ container.addEventListener("pointercancel", stopPointer);
118
+ container.addEventListener("click", handleClick);
119
+ renderSvg();
120
+ return {
121
+ getState() {
122
+ return { ...state };
123
+ },
124
+ setState(nextState) {
125
+ updateState({
126
+ ...state,
127
+ ...nextState,
128
+ });
129
+ if ("selectedObjectId" in nextState) {
130
+ renderSvg();
131
+ }
132
+ },
133
+ setRenderOptions(nextOptions) {
134
+ renderOptions = {
135
+ ...renderOptions,
136
+ ...nextOptions,
137
+ };
138
+ renderSvg();
139
+ },
140
+ fitToSystem() {
141
+ updateState(fitViewerState(scene, state, constraints));
142
+ },
143
+ destroy() {
144
+ if (destroyed) {
145
+ return;
146
+ }
147
+ destroyed = true;
148
+ container.removeEventListener("wheel", handleWheel);
149
+ container.removeEventListener("pointerdown", handlePointerDown);
150
+ container.removeEventListener("pointermove", handlePointerMove);
151
+ container.removeEventListener("pointerup", stopPointer);
152
+ container.removeEventListener("pointercancel", stopPointer);
153
+ container.removeEventListener("click", handleClick);
154
+ if (previousTabIndex === null) {
155
+ container.removeAttribute("tabindex");
156
+ }
157
+ container.style.touchAction = previousTouchAction;
158
+ container.replaceChildren();
159
+ svgElement = null;
160
+ cameraRoot = null;
161
+ },
162
+ };
163
+ function renderSvg() {
164
+ if (destroyed) {
165
+ return;
166
+ }
167
+ container.innerHTML = renderSceneToSvg(scene, {
168
+ width: renderOptions.width,
169
+ height: renderOptions.height,
170
+ padding: renderOptions.padding,
171
+ preset: renderOptions.preset,
172
+ theme: renderOptions.theme,
173
+ layers: renderOptions.layers,
174
+ subtitle: renderOptions.subtitle,
175
+ selectedObjectId: state.selectedObjectId,
176
+ });
177
+ svgElement = container.querySelector("svg");
178
+ cameraRoot = container.querySelector(`[data-worldorbit-camera-root="${WORLD_LAYER_ID}"]`);
179
+ applyTransform();
180
+ }
181
+ function applyTransform() {
182
+ cameraRoot?.setAttribute("transform", composeViewerTransform(scene, state));
183
+ }
184
+ function updateState(nextState) {
185
+ state = nextState;
186
+ applyTransform();
187
+ }
188
+ function getScenePointFromClient(clientX, clientY) {
189
+ const rect = svgElement?.getBoundingClientRect();
190
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
191
+ return { x: scene.width / 2, y: scene.height / 2 };
192
+ }
193
+ return {
194
+ x: ((clientX - rect.left) / rect.width) * scene.width,
195
+ y: ((clientY - rect.top) / rect.height) * scene.height,
196
+ };
197
+ }
198
+ }
199
+ function clamp(value, min, max) {
200
+ return Math.min(Math.max(value, min), max);
201
+ }
@@ -1,4 +1,4 @@
1
- import { type RenderScene, type WorldOrbitDocument } from "@worldorbit/core";
1
+ import { type RenderScene, type WorldOrbitDocument } from "@worldorbit/core/types";
2
2
  import type { SvgRenderOptions } from "./types.js";
3
3
  export declare const WORLD_LAYER_ID = "worldorbit-camera-root";
4
4
  export declare function renderSceneToSvg(scene: RenderScene, options?: SvgRenderOptions): string;
@@ -1,4 +1,5 @@
1
- import { loadWorldOrbitSource, renderDocumentToScene, } from "@worldorbit/core";
1
+ import { loadWorldOrbitSource } from "@worldorbit/core/load";
2
+ import { renderDocumentToScene } from "@worldorbit/core/scene";
2
3
  import { computeVisibleObjectIds } from "./atlas-state.js";
3
4
  import { resolveLayers, resolveTheme } from "./theme.js";
4
5
  export const WORLD_LAYER_ID = "worldorbit-camera-root";
@@ -1,4 +1,4 @@
1
- import { type CoordinatePoint, type RenderBounds, type RenderScene } from "@worldorbit/core";
1
+ import { type CoordinatePoint, type RenderBounds, type RenderScene } from "@worldorbit/core/types";
2
2
  import type { ViewerState } from "./types.js";
3
3
  export interface ViewerConstraints {
4
4
  minScale: number;
@@ -1,4 +1,4 @@
1
- import { rotatePoint, } from "@worldorbit/core";
1
+ import { rotatePoint } from "@worldorbit/core/scene";
2
2
  export const DEFAULT_VIEWER_STATE = {
3
3
  scale: 1,
4
4
  rotationDeg: 0,
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hanjo Winter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+
2
+
3
+ Treat your Obsidian Vault as a stellar atlas. WorldOrbit provides a text-first DSL (Domain Specific Language) to render fictional star systems, orbital mechanics, and space infrastructure directly inside your notes.
4
+
5
+ See the **Examples** section below for a quick start, or the [full reference](https://github.com/negurvulkan/worldorbit) for all the details.
6
+
7
+ ## Examples
8
+
9
+ Render a basic star system with a star and a planet:
10
+
11
+ Code-Snippet
12
+
13
+ ```
14
+ schema 2.6
15
+ system Sol
16
+
17
+ object star Sun
18
+ radius 695700km
19
+
20
+ object planet Earth
21
+ orbit Sun
22
+ semiMajor 1au
23
+ color #6fa8ff
24
+ ```
25
+
26
+
27
+ *Visualisierung eines einfachen Sternensystems in der Obsidian Live-Vorschau.*
28
+
29
+ Visualize complex orbital hierarchies including moons and rings:
30
+
31
+ Code-Snippet
32
+
33
+ ```
34
+ schema 2.6
35
+ system Sol
36
+
37
+ object star Sun
38
+ radius 695700km
39
+
40
+ object planet Earth
41
+ orbit Sun
42
+ semiMajor 1au
43
+ color #6fa8ff
44
+
45
+ object planet Saturn
46
+ orbit Sun
47
+ semiMajor 9.58au
48
+
49
+ object ring Saturn-Rings
50
+ orbit Saturn
51
+ inner 67000km
52
+ outer 140000km
53
+
54
+ object moon Titan
55
+ orbit Saturn
56
+ distance 1221870km
57
+ ```
58
+
59
+
60
+ *Darstellung komplexer orbitaler Hierarchien mit Ringen und Monden.*
61
+
62
+ ## Usage
63
+
64
+ WorldOrbit consists of two major aspects: **Data (DSL)** and **Visualizing**.
65
+
66
+ ### Data
67
+
68
+ WorldOrbit generates diagrams from fenced code blocks using the `.ks` (KeplerScript) syntax. It is designed for fictional worldbuilding, focusing on readability and logic rather than strict astrophysical simulation.
69
+
70
+ - **Objects**: Define stars, planets, moons, or space stations.
71
+ - **Placement**: Use `orbit`, `at` (for Lagrange points or anchors), or `surface` to position objects.
72
+ - **Physical Traits**: Assign `mass`, `radius`, `color`, and `atmosphere` to enrich the simulation.
73
+
74
+ ### Visualizing
75
+
76
+ Once you've defined your system, the plugin renders it using three modes:
77
+
78
+ 1. **Reading View & Live Preview**: Automatically turns code blocks into interactive 2D diagrams.
79
+ 2. **Lazy Loading**: Previews are only rendered when they become visible, keeping your Obsidian vault fast and responsive.
80
+ 3. **Locked Mode**: By default, diagrams are locked to allow safe scrolling. Click "Activate Interaction" to enable pan and zoom.
81
+
82
+ ## Features
83
+
84
+ - **2D Interactive Viewer**: Pan, zoom, and explore your systems.
85
+ - **Diagnostics**: Real-time validation of your orbital data. If a planet's period doesn't match its mass (Kepler's Laws), the plugin will tell you.
86
+ - **Command Palette**: Use `WorldOrbit: Insert Solar System Example` to quickly bootstrap a new system.
87
+ - **Fullscreen Mode**: Open any diagram in a high-resolution modal view for detailed inspection.
88
+
89
+ ## Installation
90
+
91
+ ### Community Plugin
92
+
93
+ Search for **WorldOrbit** in the Obsidian Community Plugins settings and click Install. (Note: Submission pending).
94
+
95
+ ### Manual Installation
96
+
97
+ 1. Download `main.js`, `manifest.json`, and `styles.css` from the [latest release](https://github.com/negurvulkan/worldorbit/releases).
98
+ 2. Create a folder `.obsidian/plugins/worldorbit` in your vault.
99
+ 3. Move the files into that folder and enable the plugin in Obsidian.
100
+
101
+ ## Contributing
102
+
103
+ Contributions via bug reports, documentation, and improvements are welcome.
104
+
105
+ ### Local Development
106
+
107
+ The codebase is part of a TypeScript monorepo. To set up locally:
108
+
109
+ Bash
110
+
111
+ ```
112
+ git clone https://github.com/negurvulkan/worldorbit.git
113
+ cd worldorbit
114
+ npm install
115
+ npm run build
116
+ ```
117
+
118
+ ## License
119
+
120
+ Licensed under the **MIT License**.
121
+
122
+ NPM Version|97
123
+ Downloads
124
+ GitHub stars