@nebula-spatial/viewer 0.2.4 → 0.4.0

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.
@@ -0,0 +1,208 @@
1
+ var E = Object.defineProperty;
2
+ var M = (r, t, i) => t in r ? E(r, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : r[t] = i;
3
+ var p = (r, t, i) => M(r, typeof t != "symbol" ? t + "" : t, i);
4
+ import T from "@mujoco/mujoco";
5
+ import I from "@mujoco/mujoco/mujoco.wasm?url";
6
+ let w = 0;
7
+ function F(r, t) {
8
+ const i = `/viewer-simulation-${++w}`;
9
+ r.mkdir(i);
10
+ const n = /* @__PURE__ */ new Set([i]), o = /* @__PURE__ */ new Set(), a = (e, s) => {
11
+ const c = e.replaceAll("\\", "/").split("/");
12
+ if (c[0] === "" || c.some((u) => u === ".." || u.includes(":"))) throw b(`Invalid package path: ${e}`, "E_SIMULATION_PATH");
13
+ const l = c.filter((u) => u && u !== ".");
14
+ if (!l.length) throw b("Package path is empty.", "E_SIMULATION_PATH");
15
+ let m = i;
16
+ for (const u of l.slice(0, -1))
17
+ m += `/${u}`, n.has(m) || (r.mkdir(m), n.add(m));
18
+ const d = `${i}/${l.join("/")}`;
19
+ return r.writeFile(d, s), o.add(d), d;
20
+ }, h = () => {
21
+ for (const e of o) r.unlink(e);
22
+ o.clear();
23
+ for (const e of [...n].reverse()) r.rmdir(e);
24
+ n.clear();
25
+ };
26
+ try {
27
+ for (const [e, s] of t) a(e, s);
28
+ return { writeEntry: (e) => a("__viewer_entry.xml", e), dispose: h };
29
+ } catch (e) {
30
+ throw h(), e;
31
+ }
32
+ }
33
+ function b(r, t) {
34
+ return Object.assign(new Error(r), { code: t });
35
+ }
36
+ function O({
37
+ target: r,
38
+ point: t,
39
+ velocity: i,
40
+ stiffness: n,
41
+ damping: o,
42
+ maxForce: a,
43
+ strength: h
44
+ }) {
45
+ const e = [
46
+ n * (r[0] - t[0]) - o * i[0],
47
+ n * (r[1] - t[1]) - o * i[1],
48
+ n * (r[2] - t[2]) - o * i[2]
49
+ ], s = Math.hypot(e[0], e[1], e[2]), c = a * h;
50
+ if (s > c && s > 0) {
51
+ const l = c / s;
52
+ e[0] *= l, e[1] *= l, e[2] *= l;
53
+ }
54
+ return e;
55
+ }
56
+ const f = 1 / 240, A = 0.05, v = 80, x = 1.15, g = 3.5, y = 14;
57
+ class C {
58
+ constructor() {
59
+ p(this, "mujoco", null);
60
+ p(this, "model", null);
61
+ p(this, "data", null);
62
+ p(this, "grab", null);
63
+ p(this, "lastGrabForce", [0, 0, 0]);
64
+ p(this, "velocityBuffer", new Float64Array(6));
65
+ p(this, "initialControls", []);
66
+ p(this, "accumulator", 0);
67
+ p(this, "initialVelocities", []);
68
+ }
69
+ async initialize(t, i) {
70
+ var h;
71
+ this.mujoco ?? (this.mujoco = await T({ locateFile: (e) => e.endsWith(".wasm") ? i ? new URL(e, i).href : I : e }));
72
+ const n = F(this.mujoco.FS, t.files);
73
+ let o = null, a = null;
74
+ try {
75
+ o = this.mujoco.MjModel.from_xml_path(n.writeEntry(t.xml));
76
+ for (const e of t.massOverrides ?? []) {
77
+ const s = this.mujoco.mj_name2id(o, this.mujoco.mjtObj.mjOBJ_BODY.value, e.body);
78
+ if (s <= 0 || !Number.isFinite(e.mass) || e.mass <= 0 || !(o.body_mass[s] > 0)) throw new Error(`Invalid mass override: ${e.body}`);
79
+ const c = e.mass / o.body_mass[s];
80
+ o.body_mass[s] = e.mass;
81
+ for (let l = 0; l < 3; l++) o.body_inertia[s * 3 + l] *= c;
82
+ }
83
+ a = new this.mujoco.MjData(o), (h = t.massOverrides) != null && h.length && this.mujoco.mj_setConst(o, a), o.opt.timestep = f, this.applyInitialControls(a, t.initialControls), this.applyInitialVelocities(o, a, t.initialVelocities), this.mujoco.mj_forward(o, a);
84
+ } catch (e) {
85
+ throw a == null || a.delete(), o == null || o.delete(), _(e, "E_MUJOCO_INITIALIZE");
86
+ } finally {
87
+ n.dispose();
88
+ }
89
+ this.disposeModel(), this.model = o, this.data = a, this.initialControls = t.initialControls ?? [], this.initialVelocities = t.initialVelocities ?? [], this.accumulator = 0;
90
+ }
91
+ beginGrab({
92
+ bodyId: t,
93
+ localPoint: i,
94
+ target: n,
95
+ stiffness: o = v,
96
+ damping: a,
97
+ maxForce: h,
98
+ dampingRatio: e = x,
99
+ maxTargetSpeed: s = g,
100
+ targetResponse: c = y
101
+ }) {
102
+ var j;
103
+ const l = Math.max(((j = this.model) == null ? void 0 : j.body_mass[t]) ?? 1, 1e-3), m = a ?? 2 * Math.sqrt(o * l) * e, d = h ?? Math.max(120, l * 60), u = [n[0], n[1], n[2]];
104
+ this.grab = {
105
+ bodyId: t,
106
+ localPoint: [i[0], i[1], i[2]],
107
+ target: [...u],
108
+ requestedTarget: u,
109
+ stiffness: o,
110
+ damping: m,
111
+ maxForce: d,
112
+ strength: 1,
113
+ maxTargetSpeed: s,
114
+ targetResponse: c
115
+ };
116
+ }
117
+ updateGrabTarget(t) {
118
+ this.grab && (this.grab.requestedTarget = [t[0], t[1], t[2]]);
119
+ }
120
+ updateGrabStrength(t) {
121
+ this.grab && (this.grab.strength = t);
122
+ }
123
+ applyGrabForce() {
124
+ if (!this.grab || !this.model || !this.data || !this.mujoco) return;
125
+ const { bodyId: t, localPoint: i, target: n, stiffness: o, damping: a, maxForce: h, strength: e } = this.grab, s = this.data.xmat.subarray(t * 9, t * 9 + 9), c = this.data.xpos.subarray(t * 3, t * 3 + 3), l = [
126
+ c[0] + s[0] * i[0] + s[1] * i[1] + s[2] * i[2],
127
+ c[1] + s[3] * i[0] + s[4] * i[1] + s[5] * i[2],
128
+ c[2] + s[6] * i[0] + s[7] * i[1] + s[8] * i[2]
129
+ ];
130
+ this.mujoco.mj_objectVelocity(
131
+ this.model,
132
+ this.data,
133
+ this.mujoco.mjtObj.mjOBJ_BODY.value,
134
+ t,
135
+ this.velocityBuffer,
136
+ 0
137
+ );
138
+ const m = this.velocityBuffer, d = [
139
+ l[0] - c[0],
140
+ l[1] - c[1],
141
+ l[2] - c[2]
142
+ ], u = [
143
+ m[3] + m[1] * d[2] - m[2] * d[1],
144
+ m[4] + m[2] * d[0] - m[0] * d[2],
145
+ m[5] + m[0] * d[1] - m[1] * d[0]
146
+ ], j = O({ target: n, point: l, velocity: u, stiffness: o, damping: a, maxForce: h, strength: e });
147
+ this.lastGrabForce = [...j], this.data.qfrc_applied.fill(0), this.mujoco.mj_applyFT(
148
+ this.model,
149
+ this.data,
150
+ j,
151
+ [0, 0, 0],
152
+ l,
153
+ t,
154
+ this.data.qfrc_applied
155
+ );
156
+ }
157
+ endGrab() {
158
+ this.data && this.data.qfrc_applied.fill(0), this.lastGrabForce = [0, 0, 0], this.grab = null;
159
+ }
160
+ step(t) {
161
+ if (!this.mujoco || !this.model || !this.data || t <= 0) return !1;
162
+ this.accumulator += Math.min(t, A);
163
+ let i = !1;
164
+ for (; this.accumulator >= f; )
165
+ this.updateGrabTargetForStep(), this.applyGrabForce(), this.mujoco.mj_step(this.model, this.data), this.accumulator -= f, i = !0;
166
+ return i;
167
+ }
168
+ updateGrabTargetForStep() {
169
+ const t = this.grab;
170
+ if (!t) return;
171
+ const i = t.requestedTarget ?? t.target, n = (t.maxTargetSpeed ?? g) * Math.max(1, Math.sqrt(t.strength)), o = t.targetResponse ?? y, a = 1 - Math.exp(-o * f), h = n * f;
172
+ for (let e = 0; e < 3; e++) {
173
+ const s = i[e] - t.target[e], c = Math.sign(s) * Math.min(Math.abs(s) * a, h);
174
+ t.target[e] += c;
175
+ }
176
+ }
177
+ reset() {
178
+ !this.mujoco || !this.model || !this.data || (this.endGrab(), this.mujoco.mj_resetData(this.model, this.data), this.applyInitialControls(this.data, this.initialControls), this.applyInitialVelocities(this.model, this.data, this.initialVelocities), this.mujoco.mj_forward(this.model, this.data), this.accumulator = 0);
179
+ }
180
+ dispose() {
181
+ this.disposeModel(), this.mujoco = null;
182
+ }
183
+ applyInitialControls(t, i) {
184
+ if (i != null && i.length) {
185
+ if (i.length !== t.ctrl.length) throw _(new Error(`Initial control count ${i.length} does not match MuJoCo actuator count ${t.ctrl.length}.`), "E_MUJOCO_INITIALIZE");
186
+ t.ctrl.set(i);
187
+ }
188
+ }
189
+ applyInitialVelocities(t, i, n) {
190
+ for (const o of n ?? []) {
191
+ const a = this.mujoco.mj_name2id(t, this.mujoco.mjtObj.mjOBJ_BODY.value, o.body), h = a >= 0 ? t.body_jntadr[a] : -1;
192
+ if (h < 0 || t.jnt_type[h] !== this.mujoco.mjtJoint.mjJNT_FREE.value || o.velocity.length !== 6 || !o.velocity.every(Number.isFinite))
193
+ throw _(new Error(`Invalid free-body initial velocity: ${o.body}`), "E_MUJOCO_INITIALIZE");
194
+ i.qvel.set(o.velocity, t.jnt_dofadr[h]);
195
+ }
196
+ }
197
+ disposeModel() {
198
+ var t, i;
199
+ this.endGrab(), (t = this.data) == null || t.delete(), (i = this.model) == null || i.delete(), this.data = null, this.model = null, this.initialControls = [], this.initialVelocities = [], this.accumulator = 0;
200
+ }
201
+ }
202
+ function _(r, t) {
203
+ const i = r instanceof Error ? r : new Error(String(r));
204
+ return Object.assign(i, { code: t });
205
+ }
206
+ export {
207
+ C as MujocoRuntime
208
+ };
@@ -0,0 +1,175 @@
1
+ import type { Box3, Object3D } from 'three';
2
+ import type { CadHit, CadSelection, CadViewerSnapshot, CadViewerEventMap } from './cad/types';
3
+ import type { ViewerEventMap, ViewerViewMode, ViewerUiTheme } from './types';
4
+ import type { AssetControlState, AssetLoaderContribution, AssetPreviewSnapshot, AssetUpAxis } from './assets/types';
5
+ export interface Disposable {
6
+ dispose(): void;
7
+ }
8
+ export interface ToolbarItem {
9
+ readonly id: string;
10
+ readonly label: string;
11
+ readonly title?: string;
12
+ readonly disabled?: boolean;
13
+ readonly pressed?: boolean;
14
+ /** Inline SVG markup (24x24 viewBox, `currentColor` stroke). */
15
+ readonly icon?: string;
16
+ readonly onClick: () => void;
17
+ }
18
+ export interface ViewerState {
19
+ readonly viewMode: ViewerViewMode;
20
+ readonly selectedObject: Object3D | null;
21
+ readonly highlightedObject: Object3D | null;
22
+ readonly isDisposed: boolean;
23
+ }
24
+ /** Read-only view of the active asset. Never exposes the mutable `AssetPreview` handle. */
25
+ export interface ViewerAssetReadApi {
26
+ /** Snapshot of the active asset; `null` when no asset is open. */
27
+ $read(): AssetPreviewSnapshot | null;
28
+ /** Authoritative control state, safe to bind button pressed states to. */
29
+ $readControl(): AssetControlState;
30
+ }
31
+ export interface ViewerPluginContext {
32
+ readonly cad: {
33
+ $read(): CadViewerSnapshot;
34
+ };
35
+ readonly asset: ViewerAssetReadApi;
36
+ readonly viewer: {
37
+ $getState(): ViewerState;
38
+ };
39
+ readonly commands: {
40
+ setLayerVisible(index: number, visible: boolean): boolean;
41
+ setSelection(selection: CadSelection | null): void;
42
+ fitDocument(): boolean;
43
+ setViewMode(mode: ViewerViewMode): void;
44
+ play(): boolean;
45
+ pause(): boolean;
46
+ reset(): boolean;
47
+ fitView(): boolean;
48
+ setUpAxis(axis: AssetUpAxis): boolean;
49
+ setGroundVisible(visible: boolean): boolean;
50
+ setCollisionVisible(visible: boolean): boolean;
51
+ setTheme(theme: ViewerUiTheme): boolean;
52
+ };
53
+ readonly events: {
54
+ on<K extends keyof (ViewerEventMap & CadViewerEventMap)>(type: K, listener: (event: (ViewerEventMap & CadViewerEventMap)[K]) => void): () => void;
55
+ };
56
+ readonly layout: {
57
+ mount(node: Node, options?: {
58
+ position?: 'left' | 'right' | 'top' | 'bottom' | 'overlay';
59
+ }): Disposable;
60
+ onInsetsChange(listener: (insets: ViewerEventMap['ui-layout-change']['contentInsets']) => void): () => void;
61
+ };
62
+ contribute(point: ViewerContributionPoint): Disposable;
63
+ dispose(): void;
64
+ }
65
+ export interface ViewerInteractionHooks {
66
+ /** Return false to consume the interaction and stop the managed pipeline. */
67
+ onPick?: (hit: CadHit | null) => boolean | void;
68
+ onHover?: (hit: CadHit | null) => void;
69
+ onSelect?: (selection: CadSelection | null) => void;
70
+ onClear?: () => void;
71
+ }
72
+ export type ViewerContributionPoint = {
73
+ readonly kind: 'panel';
74
+ readonly id: string;
75
+ readonly factory: (ctx: ViewerPluginContext) => Node;
76
+ } | {
77
+ readonly kind: 'toolbar';
78
+ readonly id: string;
79
+ readonly items: readonly ToolbarItem[];
80
+ } | {
81
+ readonly kind: 'hud';
82
+ readonly id: string;
83
+ readonly factory: (ctx: ViewerPluginContext) => Node;
84
+ } | {
85
+ readonly kind: 'statusbar';
86
+ readonly id: string;
87
+ readonly factory: (ctx: ViewerPluginContext) => Node;
88
+ } | {
89
+ readonly kind: 'inspector';
90
+ readonly id: string;
91
+ readonly matcher: (selection: CadSelection) => boolean;
92
+ readonly render: (ctx: ViewerPluginContext, selection: CadSelection) => Node;
93
+ } | {
94
+ readonly kind: 'interaction';
95
+ readonly id: string;
96
+ readonly hooks: ViewerInteractionHooks;
97
+ } | {
98
+ readonly kind: 'command';
99
+ readonly id: string;
100
+ readonly handler: (ctx: ViewerPluginContext, args?: unknown) => void;
101
+ } | {
102
+ readonly kind: 'fit-policy';
103
+ readonly id: string;
104
+ readonly resolve: (ctx: ViewerPluginContext) => Box3 | null;
105
+ } | AssetLoaderContribution;
106
+ export interface ViewerPlugin {
107
+ readonly id: string;
108
+ activate(context: ViewerPluginContext): void | Promise<void>;
109
+ deactivate?(): void | Promise<void>;
110
+ }
111
+ type SnapshotReader = () => CadViewerSnapshot;
112
+ type StateReader = () => ViewerState;
113
+ type AssetReader = () => AssetPreviewSnapshot | null;
114
+ type ControlStateReader = () => AssetControlState;
115
+ type PluginEventMap = ViewerEventMap & CadViewerEventMap;
116
+ type EventSubscriber = <K extends keyof PluginEventMap>(type: K, listener: (event: PluginEventMap[K]) => void) => () => void;
117
+ type CommandApi = ViewerPluginContext['commands'];
118
+ /**
119
+ * Layout channel the plugin manager delegates to. The engine owns zone
120
+ * containers, insets computation and the `ui-layout-change` broadcast; the
121
+ * manager only forwards contribution nodes into it.
122
+ */
123
+ export interface PluginLayoutPort {
124
+ mountPlugin(node: Node, position?: 'left' | 'right' | 'top' | 'bottom' | 'overlay'): Disposable;
125
+ /** Built-in UI path: the package owns the styling of nodes mounted this way. */
126
+ mountBuiltin(node: Node, options: {
127
+ zone: 'left' | 'right' | 'top' | 'bottom' | 'overlay';
128
+ group?: string;
129
+ }): Disposable;
130
+ }
131
+ export declare class ViewerPluginManager implements ViewerPluginRegistry {
132
+ private readonly readCad;
133
+ private readonly readState;
134
+ private readonly readAsset;
135
+ private readonly readControlState;
136
+ private readonly subscribe;
137
+ private readonly commands;
138
+ private readonly hostElement;
139
+ private readonly layout;
140
+ private readonly pluginsById;
141
+ private readonly contributions;
142
+ private readonly commandContributions;
143
+ private readonly builtinPlugins;
144
+ private readonly contributionRemovedListeners;
145
+ private refreshingInspectors;
146
+ private disposed;
147
+ constructor(readCad: SnapshotReader, readState: StateReader, readAsset: AssetReader, readControlState: ControlStateReader, subscribe: EventSubscriber, commands: CommandApi, hostElement: HTMLElement, layout?: PluginLayoutPort | null);
148
+ register(plugin: ViewerPlugin): Disposable;
149
+ use(plugins: readonly ViewerPlugin[], options?: {
150
+ replace?: boolean;
151
+ }): void;
152
+ registerBuiltin(plugin: ViewerPlugin): Disposable;
153
+ getAssetLoaders(): readonly AssetLoaderContribution[];
154
+ onAssetLoaderRemoved(listener: (loader: AssetLoaderContribution) => void): Disposable;
155
+ private onContributionRemoved;
156
+ private deactivate;
157
+ disposeAll(): void;
158
+ dispose(): void;
159
+ dispatchInteraction(kind: keyof ViewerInteractionHooks, value: CadHit | CadSelection | null): boolean;
160
+ findFitPolicy(): Box3 | null;
161
+ executeCommand(id: string, args?: unknown): boolean;
162
+ hasCommand(id: string): boolean;
163
+ private removeContribution;
164
+ private mountContribution;
165
+ private isFirstMatchingInspector;
166
+ private refreshInspectorContributions;
167
+ private assertActive;
168
+ }
169
+ export interface ViewerPluginRegistry {
170
+ register(plugin: ViewerPlugin): Disposable;
171
+ use(plugins: readonly ViewerPlugin[], options?: {
172
+ replace?: boolean;
173
+ }): void;
174
+ }
175
+ export {};