@holoscript/openusd-plugin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @holoscript/openusd-plugin — OpenUSD interop baseline.
3
+ *
4
+ * Targets the paper-12 "OpenUSD proxy LOC" bucket by providing a real USDA
5
+ * export from a .holo composition tree. Current scope: deterministic USDA
6
+ * export, semantic receipt emission, optional pxr/usdchecker validation, and
7
+ * deterministic round-trip conformance checks that do not require local pxr
8
+ * bindings.
9
+ *
10
+ * Status: BASELINE+OPTIONAL_PXR. pxr/usdchecker validation is opt-in.
11
+ * Research: ai-ecosystem/research/2026-04-23_openusd-holoscript-robotics-frontend.md
12
+ * Paper: memory/paper-12-plugin-openusd-probe.md
13
+ */
14
+ type UsdaStageKind = 'world' | 'anim' | 'look';
15
+ type UsdaPrimitiveKind = 'xform' | 'mesh' | 'light';
16
+ type UsdaAttrValue = string | number | boolean | number[];
17
+ interface UsdaSemanticReceipt {
18
+ role: string;
19
+ sourcePath: string;
20
+ dtId?: string;
21
+ units?: string;
22
+ telemetry?: string[];
23
+ simulationContract?: string;
24
+ provenance?: string;
25
+ }
26
+ interface UsdaPrimitiveInput {
27
+ kind: UsdaPrimitiveKind;
28
+ path: string;
29
+ attrs?: Record<string, UsdaAttrValue>;
30
+ semantic?: UsdaSemanticReceipt;
31
+ }
32
+ interface UsdaExportInput {
33
+ name: string;
34
+ stage?: UsdaStageKind;
35
+ upAxis?: 'Y' | 'Z';
36
+ metersPerUnit?: number;
37
+ defaultPrim?: string;
38
+ customData?: Record<string, string>;
39
+ primitives?: UsdaPrimitiveInput[];
40
+ }
41
+ interface UsdaExportOutput {
42
+ usda: string;
43
+ loc: number;
44
+ primitive_count: number;
45
+ semantic_receipt_count: number;
46
+ semantic_hash: string;
47
+ }
48
+ interface UsdaRoundTripSummary {
49
+ primitiveNames: string[];
50
+ semanticSourcePaths: string[];
51
+ semanticRoles: string[];
52
+ semanticHash?: string;
53
+ }
54
+ interface UsdaConformanceCheck {
55
+ id: string;
56
+ passed: boolean;
57
+ message: string;
58
+ }
59
+ type UsdaConformanceValidator = 'syntax-roundtrip' | 'pxr.usdchecker';
60
+ type UsdaConformanceReceiptStatus = 'passed' | 'failed' | 'unavailable';
61
+ type UsdaConformanceMode = 'syntax-roundtrip' | 'pxr-usdchecker';
62
+ interface UsdaConformanceReceipt {
63
+ validator: UsdaConformanceValidator;
64
+ status: UsdaConformanceReceiptStatus;
65
+ mode: UsdaConformanceMode;
66
+ semantic_hash: string;
67
+ primitive_count: number;
68
+ message: string;
69
+ command?: string;
70
+ exitCode?: number | null;
71
+ signal?: string | null;
72
+ stdout?: string;
73
+ stderr?: string;
74
+ }
75
+ interface UsdCheckerProcessResult {
76
+ status?: number | null;
77
+ signal?: string | null;
78
+ stdout?: unknown;
79
+ stderr?: unknown;
80
+ error?: unknown;
81
+ }
82
+ interface UsdCheckerRunOptions {
83
+ timeoutMs: number;
84
+ }
85
+ type UsdCheckerRunner = (command: string, args: string[], options: UsdCheckerRunOptions) => UsdCheckerProcessResult;
86
+ interface UsdCheckerValidationOptions {
87
+ enabled?: boolean;
88
+ command?: string;
89
+ args?: string[];
90
+ timeoutMs?: number;
91
+ runner?: UsdCheckerRunner;
92
+ }
93
+ interface UsdaConformanceOptions {
94
+ usdchecker?: UsdCheckerValidationOptions;
95
+ }
96
+ interface UsdaConformanceReport {
97
+ passed: boolean;
98
+ checks: UsdaConformanceCheck[];
99
+ receipts: UsdaConformanceReceipt[];
100
+ validationMode: UsdaConformanceMode;
101
+ output: UsdaExportOutput;
102
+ roundTrip: UsdaRoundTripSummary;
103
+ }
104
+ /**
105
+ * Export a minimal .holo-derived scene to USDA (ASCII USD) text.
106
+ * Structure is valid USDA text; HoloScript semantics are preserved as custom
107
+ * namespaced attributes so Omniverse/USD ingestion can round-trip receipts.
108
+ */
109
+ declare function exportToUsda(input: UsdaExportInput): UsdaExportOutput;
110
+ /** Minimal round-trip probe — re-parse emitted USDA to verify syntactic stability. */
111
+ declare function usdaStableRoundTrip(input: UsdaExportInput): boolean;
112
+ declare function summarizeUsdaRoundTrip(usda: string): UsdaRoundTripSummary;
113
+ declare function runOpenUsdConformanceRoundTrip(input: UsdaExportInput, options?: UsdaConformanceOptions): UsdaConformanceReport;
114
+ declare function buildIndustrialDigitalTwinFixture(): UsdaExportInput;
115
+
116
+ export { type UsdCheckerProcessResult, type UsdCheckerRunOptions, type UsdCheckerRunner, type UsdCheckerValidationOptions, type UsdaAttrValue, type UsdaConformanceCheck, type UsdaConformanceMode, type UsdaConformanceOptions, type UsdaConformanceReceipt, type UsdaConformanceReceiptStatus, type UsdaConformanceReport, type UsdaConformanceValidator, type UsdaExportInput, type UsdaExportOutput, type UsdaPrimitiveInput, type UsdaPrimitiveKind, type UsdaRoundTripSummary, type UsdaSemanticReceipt, type UsdaStageKind, buildIndustrialDigitalTwinFixture, exportToUsda, runOpenUsdConformanceRoundTrip, summarizeUsdaRoundTrip, usdaStableRoundTrip };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @holoscript/openusd-plugin — OpenUSD interop baseline.
3
+ *
4
+ * Targets the paper-12 "OpenUSD proxy LOC" bucket by providing a real USDA
5
+ * export from a .holo composition tree. Current scope: deterministic USDA
6
+ * export, semantic receipt emission, optional pxr/usdchecker validation, and
7
+ * deterministic round-trip conformance checks that do not require local pxr
8
+ * bindings.
9
+ *
10
+ * Status: BASELINE+OPTIONAL_PXR. pxr/usdchecker validation is opt-in.
11
+ * Research: ai-ecosystem/research/2026-04-23_openusd-holoscript-robotics-frontend.md
12
+ * Paper: memory/paper-12-plugin-openusd-probe.md
13
+ */
14
+ type UsdaStageKind = 'world' | 'anim' | 'look';
15
+ type UsdaPrimitiveKind = 'xform' | 'mesh' | 'light';
16
+ type UsdaAttrValue = string | number | boolean | number[];
17
+ interface UsdaSemanticReceipt {
18
+ role: string;
19
+ sourcePath: string;
20
+ dtId?: string;
21
+ units?: string;
22
+ telemetry?: string[];
23
+ simulationContract?: string;
24
+ provenance?: string;
25
+ }
26
+ interface UsdaPrimitiveInput {
27
+ kind: UsdaPrimitiveKind;
28
+ path: string;
29
+ attrs?: Record<string, UsdaAttrValue>;
30
+ semantic?: UsdaSemanticReceipt;
31
+ }
32
+ interface UsdaExportInput {
33
+ name: string;
34
+ stage?: UsdaStageKind;
35
+ upAxis?: 'Y' | 'Z';
36
+ metersPerUnit?: number;
37
+ defaultPrim?: string;
38
+ customData?: Record<string, string>;
39
+ primitives?: UsdaPrimitiveInput[];
40
+ }
41
+ interface UsdaExportOutput {
42
+ usda: string;
43
+ loc: number;
44
+ primitive_count: number;
45
+ semantic_receipt_count: number;
46
+ semantic_hash: string;
47
+ }
48
+ interface UsdaRoundTripSummary {
49
+ primitiveNames: string[];
50
+ semanticSourcePaths: string[];
51
+ semanticRoles: string[];
52
+ semanticHash?: string;
53
+ }
54
+ interface UsdaConformanceCheck {
55
+ id: string;
56
+ passed: boolean;
57
+ message: string;
58
+ }
59
+ type UsdaConformanceValidator = 'syntax-roundtrip' | 'pxr.usdchecker';
60
+ type UsdaConformanceReceiptStatus = 'passed' | 'failed' | 'unavailable';
61
+ type UsdaConformanceMode = 'syntax-roundtrip' | 'pxr-usdchecker';
62
+ interface UsdaConformanceReceipt {
63
+ validator: UsdaConformanceValidator;
64
+ status: UsdaConformanceReceiptStatus;
65
+ mode: UsdaConformanceMode;
66
+ semantic_hash: string;
67
+ primitive_count: number;
68
+ message: string;
69
+ command?: string;
70
+ exitCode?: number | null;
71
+ signal?: string | null;
72
+ stdout?: string;
73
+ stderr?: string;
74
+ }
75
+ interface UsdCheckerProcessResult {
76
+ status?: number | null;
77
+ signal?: string | null;
78
+ stdout?: unknown;
79
+ stderr?: unknown;
80
+ error?: unknown;
81
+ }
82
+ interface UsdCheckerRunOptions {
83
+ timeoutMs: number;
84
+ }
85
+ type UsdCheckerRunner = (command: string, args: string[], options: UsdCheckerRunOptions) => UsdCheckerProcessResult;
86
+ interface UsdCheckerValidationOptions {
87
+ enabled?: boolean;
88
+ command?: string;
89
+ args?: string[];
90
+ timeoutMs?: number;
91
+ runner?: UsdCheckerRunner;
92
+ }
93
+ interface UsdaConformanceOptions {
94
+ usdchecker?: UsdCheckerValidationOptions;
95
+ }
96
+ interface UsdaConformanceReport {
97
+ passed: boolean;
98
+ checks: UsdaConformanceCheck[];
99
+ receipts: UsdaConformanceReceipt[];
100
+ validationMode: UsdaConformanceMode;
101
+ output: UsdaExportOutput;
102
+ roundTrip: UsdaRoundTripSummary;
103
+ }
104
+ /**
105
+ * Export a minimal .holo-derived scene to USDA (ASCII USD) text.
106
+ * Structure is valid USDA text; HoloScript semantics are preserved as custom
107
+ * namespaced attributes so Omniverse/USD ingestion can round-trip receipts.
108
+ */
109
+ declare function exportToUsda(input: UsdaExportInput): UsdaExportOutput;
110
+ /** Minimal round-trip probe — re-parse emitted USDA to verify syntactic stability. */
111
+ declare function usdaStableRoundTrip(input: UsdaExportInput): boolean;
112
+ declare function summarizeUsdaRoundTrip(usda: string): UsdaRoundTripSummary;
113
+ declare function runOpenUsdConformanceRoundTrip(input: UsdaExportInput, options?: UsdaConformanceOptions): UsdaConformanceReport;
114
+ declare function buildIndustrialDigitalTwinFixture(): UsdaExportInput;
115
+
116
+ export { type UsdCheckerProcessResult, type UsdCheckerRunOptions, type UsdCheckerRunner, type UsdCheckerValidationOptions, type UsdaAttrValue, type UsdaConformanceCheck, type UsdaConformanceMode, type UsdaConformanceOptions, type UsdaConformanceReceipt, type UsdaConformanceReceiptStatus, type UsdaConformanceReport, type UsdaConformanceValidator, type UsdaExportInput, type UsdaExportOutput, type UsdaPrimitiveInput, type UsdaPrimitiveKind, type UsdaRoundTripSummary, type UsdaSemanticReceipt, type UsdaStageKind, buildIndustrialDigitalTwinFixture, exportToUsda, runOpenUsdConformanceRoundTrip, summarizeUsdaRoundTrip, usdaStableRoundTrip };
package/dist/index.js ADDED
@@ -0,0 +1,451 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ buildIndustrialDigitalTwinFixture: () => buildIndustrialDigitalTwinFixture,
24
+ exportToUsda: () => exportToUsda,
25
+ runOpenUsdConformanceRoundTrip: () => runOpenUsdConformanceRoundTrip,
26
+ summarizeUsdaRoundTrip: () => summarizeUsdaRoundTrip,
27
+ usdaStableRoundTrip: () => usdaStableRoundTrip
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var import_child_process = require("child_process");
31
+ var import_fs = require("fs");
32
+ var import_os = require("os");
33
+ var import_path = require("path");
34
+ function exportToUsda(input) {
35
+ const lines = [];
36
+ const defaultPrim = sanitizeIdentifier(input.defaultPrim ?? "World");
37
+ const upAxis = input.upAxis ?? "Y";
38
+ const metersPerUnit = input.metersPerUnit ?? 1;
39
+ const stage = input.stage ?? "world";
40
+ const semanticHash = stableHash(input);
41
+ lines.push("#usda 1.0");
42
+ lines.push("(");
43
+ lines.push(` defaultPrim = "${defaultPrim}"`);
44
+ lines.push(` metersPerUnit = ${formatNumber(metersPerUnit)}`);
45
+ lines.push(` upAxis = "${upAxis}"`);
46
+ lines.push(")");
47
+ lines.push("");
48
+ lines.push(`def Xform "${defaultPrim}"`);
49
+ lines.push("{");
50
+ lines.push(` custom string holo:sourceName = "${escapeUsdString(input.name)}"`);
51
+ lines.push(` custom string holo:stage = "${stage}"`);
52
+ lines.push(` custom string holo:semanticHash = "${semanticHash}"`);
53
+ for (const [key, value] of Object.entries(input.customData ?? {})) {
54
+ lines.push(` custom string holo:${sanitizeIdentifier(key)} = "${escapeUsdString(value)}"`);
55
+ }
56
+ const prims = input.primitives ?? [{ kind: "xform", path: "root" }];
57
+ for (const prim of prims) {
58
+ const typeName = prim.kind === "mesh" ? "Mesh" : prim.kind === "light" ? "SphereLight" : "Xform";
59
+ lines.push(` def ${typeName} "${sanitizeIdentifier(prim.path)}"`);
60
+ lines.push(" {");
61
+ lines.push(` custom string holo:sourcePath = "${escapeUsdString(prim.path)}"`);
62
+ if (prim.semantic) {
63
+ pushSemanticReceipt(lines, prim.semantic, " ");
64
+ }
65
+ for (const [k, v] of Object.entries(prim.attrs ?? {})) {
66
+ lines.push(formatUsdAttribute(k, v, " "));
67
+ }
68
+ lines.push(" }");
69
+ }
70
+ lines.push("}");
71
+ lines.push("");
72
+ const usda = lines.join("\n");
73
+ const loc = lines.filter((l) => l.trim().length > 0).length;
74
+ const semanticReceiptCount = prims.filter((p) => p.semantic).length;
75
+ return {
76
+ usda,
77
+ loc,
78
+ primitive_count: input.primitives?.length ?? 1,
79
+ semantic_receipt_count: semanticReceiptCount,
80
+ semantic_hash: semanticHash
81
+ };
82
+ }
83
+ function usdaStableRoundTrip(input) {
84
+ const out = exportToUsda(input);
85
+ const prims = input.primitives ?? [];
86
+ for (const p of prims) {
87
+ const sanitized = sanitizeIdentifier(p.path);
88
+ if (!out.usda.includes(`"${sanitized}"`)) return false;
89
+ }
90
+ return true;
91
+ }
92
+ function summarizeUsdaRoundTrip(usda) {
93
+ const primitiveNames = [...usda.matchAll(/def\s+\w+\s+"([^"]+)"/g)].map((m) => m[1]);
94
+ const semanticSourcePaths = [...usda.matchAll(/custom string holo:sourcePath = "([^"]+)"/g)].map(
95
+ (m) => unescapeUsdString(m[1])
96
+ );
97
+ const semanticRoles = [...usda.matchAll(/custom string holo:role = "([^"]+)"/g)].map(
98
+ (m) => unescapeUsdString(m[1])
99
+ );
100
+ const semanticHash = usda.match(/custom string holo:semanticHash = "([^"]+)"/)?.[1];
101
+ return {
102
+ primitiveNames,
103
+ semanticSourcePaths,
104
+ semanticRoles,
105
+ semanticHash
106
+ };
107
+ }
108
+ function runOpenUsdConformanceRoundTrip(input, options = {}) {
109
+ const output = exportToUsda(input);
110
+ const roundTrip = summarizeUsdaRoundTrip(output.usda);
111
+ const prims = input.primitives ?? [{ kind: "xform", path: "root" }];
112
+ const semanticPrims = prims.filter((p) => p.semantic);
113
+ const checks = [];
114
+ const add = (id, passed, message) => checks.push({ id, passed, message });
115
+ add("magic-header", output.usda.startsWith("#usda 1.0"), "USDA file starts with #usda 1.0");
116
+ add(
117
+ "default-prim",
118
+ output.usda.includes(`defaultPrim = "${sanitizeIdentifier(input.defaultPrim ?? "World")}"`),
119
+ "Default prim is declared in the layer preamble"
120
+ );
121
+ add("up-axis", /upAxis = "[YZ]"/.test(output.usda), "Stage declares a USD upAxis");
122
+ add("meters-per-unit", /metersPerUnit = \d/.test(output.usda), "Stage declares metersPerUnit");
123
+ add(
124
+ "primitive-count",
125
+ output.primitive_count === prims.length,
126
+ "Output primitive_count matches declared primitive count"
127
+ );
128
+ add(
129
+ "primitive-roundtrip",
130
+ prims.every((p) => roundTrip.primitiveNames.includes(sanitizeIdentifier(p.path))),
131
+ "Every declared primitive survives USDA reparse by sanitized name"
132
+ );
133
+ add(
134
+ "semantic-source-paths",
135
+ prims.every((p) => roundTrip.semanticSourcePaths.includes(p.path)),
136
+ "Every primitive sourcePath receipt survives USDA reparse"
137
+ );
138
+ add(
139
+ "semantic-receipts",
140
+ output.semantic_receipt_count === semanticPrims.length && semanticPrims.every((p) => roundTrip.semanticRoles.includes(p.semantic.role)),
141
+ "Every semantic receipt role survives USDA reparse"
142
+ );
143
+ add(
144
+ "semantic-hash",
145
+ roundTrip.semanticHash === output.semantic_hash,
146
+ "Root semantic hash survives USDA reparse"
147
+ );
148
+ const syntaxPassed = checks.every((check) => check.passed);
149
+ const receipts = [
150
+ {
151
+ validator: "syntax-roundtrip",
152
+ status: syntaxPassed ? "passed" : "failed",
153
+ mode: "syntax-roundtrip",
154
+ semantic_hash: output.semantic_hash,
155
+ primitive_count: output.primitive_count,
156
+ message: syntaxPassed ? "Deterministic USDA syntax round-trip passed without requiring pxr bindings" : "Deterministic USDA syntax round-trip failed"
157
+ }
158
+ ];
159
+ const usdcheckerReceipt = runOptionalUsdChecker(output, options.usdchecker);
160
+ if (usdcheckerReceipt) {
161
+ receipts.push(usdcheckerReceipt);
162
+ add("pxr-usdchecker", usdcheckerReceipt.status !== "failed", usdcheckerReceipt.message);
163
+ }
164
+ const validationMode = receipts.some(
165
+ (receipt) => receipt.validator === "pxr.usdchecker" && receipt.status === "passed"
166
+ ) ? "pxr-usdchecker" : "syntax-roundtrip";
167
+ return {
168
+ passed: checks.every((check) => check.passed),
169
+ checks,
170
+ receipts,
171
+ validationMode,
172
+ output,
173
+ roundTrip
174
+ };
175
+ }
176
+ function buildIndustrialDigitalTwinFixture() {
177
+ return {
178
+ name: "HoloScriptFactoryCellTwin",
179
+ stage: "world",
180
+ upAxis: "Z",
181
+ metersPerUnit: 1,
182
+ defaultPrim: "FactoryCell",
183
+ customData: {
184
+ domain: "industrial_digital_twin",
185
+ targetRuntime: "omniverse_openusd",
186
+ evidence: "semantic_receipts_required"
187
+ },
188
+ primitives: [
189
+ {
190
+ kind: "xform",
191
+ path: "/FactoryCell",
192
+ semantic: {
193
+ role: "factory_cell",
194
+ sourcePath: "examples/openusd/industrial-factory-cell.holo",
195
+ dtId: "dtmi:holoscript:factory:cell;1",
196
+ units: "SI",
197
+ simulationContract: "fixed_dt_60hz;z_up;semantic_receipts_required"
198
+ }
199
+ },
200
+ {
201
+ kind: "mesh",
202
+ path: "/FactoryCell/LineA/Conveyor",
203
+ attrs: { position: [0, 0.5, 0], scale: [8, 1, 1.5], material: "conveyor_belt" },
204
+ semantic: {
205
+ role: "conveyor",
206
+ sourcePath: "object:ConveyorLineA",
207
+ dtId: "dt:conveyor:lineA:belt1",
208
+ units: "m/s",
209
+ telemetry: ["speed", "vibration", "temperature"],
210
+ simulationContract: "kinematic_belt;collision_bounds_preserved"
211
+ }
212
+ },
213
+ {
214
+ kind: "mesh",
215
+ path: "/FactoryCell/LineA/MotorM001",
216
+ attrs: { position: [-4.5, 0.8, 0], rpm: 1450, powerKW: 2.2 },
217
+ semantic: {
218
+ role: "motor",
219
+ sourcePath: "object:MotorM001",
220
+ dtId: "dt:motor:lineA:m001",
221
+ units: "rpm,kW",
222
+ telemetry: ["current", "temperature", "vibrationRMS"],
223
+ simulationContract: "predictive_maintenance_receipt_required"
224
+ }
225
+ },
226
+ {
227
+ kind: "xform",
228
+ path: "/FactoryCell/LineA/VibrationSensor",
229
+ attrs: { position: [-2, 1.25, -0.9], samplingHz: 1e3 },
230
+ semantic: {
231
+ role: "sensor",
232
+ sourcePath: "object:VibrationSensorA",
233
+ dtId: "dt:sensor:vibration:lineA:s001",
234
+ units: "mm/s",
235
+ telemetry: ["x", "y", "z"],
236
+ simulationContract: "telemetry_replay_receipt_required"
237
+ }
238
+ },
239
+ {
240
+ kind: "mesh",
241
+ path: "/FactoryCell/Safety/Cage",
242
+ attrs: { position: [0, 1, -3], scale: [10, 2, 0.1], material: "safety_fence" },
243
+ semantic: {
244
+ role: "safety_boundary",
245
+ sourcePath: "object:SafetyCage",
246
+ dtId: "dt:safety:cellA:cage",
247
+ units: "meters",
248
+ simulationContract: "collision_bounds_preserved;interlock_zone"
249
+ }
250
+ },
251
+ {
252
+ kind: "xform",
253
+ path: "/FactoryCell/Assembly/CobotArm",
254
+ attrs: { position: [3, 1.5, -2], payloadKg: 5, reachM: 0.85 },
255
+ semantic: {
256
+ role: "robot_actor",
257
+ sourcePath: "object:CobotArm",
258
+ dtId: "dt:cobot:assembly:cb001",
259
+ units: "kg,m",
260
+ telemetry: ["joint_state", "tool_pose", "safety_stop"],
261
+ simulationContract: "articulation_root;replayable_joint_commands"
262
+ }
263
+ },
264
+ {
265
+ kind: "light",
266
+ path: "/FactoryCell/Inspection/QualityLight",
267
+ attrs: { position: [0, 3, 0], intensity: 6500 },
268
+ semantic: {
269
+ role: "inspection_light",
270
+ sourcePath: "object:QualityLight",
271
+ dtId: "dt:inspection:light:q001",
272
+ units: "lumens",
273
+ simulationContract: "vision_pipeline_lighting_receipt"
274
+ }
275
+ }
276
+ ]
277
+ };
278
+ }
279
+ function pushSemanticReceipt(lines, semantic, indent) {
280
+ lines.push(`${indent}custom string holo:role = "${escapeUsdString(semantic.role)}"`);
281
+ lines.push(
282
+ `${indent}custom string holo:semanticSource = "${escapeUsdString(semantic.sourcePath)}"`
283
+ );
284
+ if (semantic.dtId)
285
+ lines.push(`${indent}custom string holo:dtId = "${escapeUsdString(semantic.dtId)}"`);
286
+ if (semantic.units)
287
+ lines.push(`${indent}custom string holo:units = "${escapeUsdString(semantic.units)}"`);
288
+ if (semantic.telemetry?.length) {
289
+ lines.push(
290
+ `${indent}custom string holo:telemetry = "${escapeUsdString(semantic.telemetry.join(","))}"`
291
+ );
292
+ }
293
+ if (semantic.simulationContract) {
294
+ lines.push(
295
+ `${indent}custom string holo:simulationContract = "${escapeUsdString(semantic.simulationContract)}"`
296
+ );
297
+ }
298
+ if (semantic.provenance) {
299
+ lines.push(
300
+ `${indent}custom string holo:provenance = "${escapeUsdString(semantic.provenance)}"`
301
+ );
302
+ }
303
+ }
304
+ function runOptionalUsdChecker(output, options) {
305
+ if (!options?.enabled) return void 0;
306
+ const command = options.command ?? process.env.HOLOSCRIPT_USDCHECKER ?? "usdchecker";
307
+ const timeoutMs = options.timeoutMs ?? 1e4;
308
+ const runner = options.runner ?? runUsdCheckerProcess;
309
+ let tempDir;
310
+ try {
311
+ tempDir = (0, import_fs.mkdtempSync)((0, import_path.join)((0, import_os.tmpdir)(), "holoscript-openusd-"));
312
+ const stagePath = (0, import_path.join)(tempDir, "stage.usda");
313
+ (0, import_fs.writeFileSync)(stagePath, output.usda, "utf8");
314
+ const result = runner(command, [...options.args ?? [], stagePath], { timeoutMs });
315
+ const errorCode = getErrorCode(result.error);
316
+ const stdout = normalizeProcessOutput(result.stdout);
317
+ const stderr = normalizeProcessOutput(result.stderr);
318
+ if (errorCode === "ENOENT") {
319
+ return {
320
+ validator: "pxr.usdchecker",
321
+ status: "unavailable",
322
+ mode: "syntax-roundtrip",
323
+ semantic_hash: output.semantic_hash,
324
+ primitive_count: output.primitive_count,
325
+ command,
326
+ message: `${command} was not found; deterministic syntax round-trip fallback used`
327
+ };
328
+ }
329
+ if (result.error) {
330
+ return {
331
+ validator: "pxr.usdchecker",
332
+ status: "failed",
333
+ mode: "pxr-usdchecker",
334
+ semantic_hash: output.semantic_hash,
335
+ primitive_count: output.primitive_count,
336
+ command,
337
+ exitCode: result.status ?? null,
338
+ signal: result.signal ?? null,
339
+ stdout,
340
+ stderr,
341
+ message: `${command} could not complete: ${getErrorMessage(result.error)}`
342
+ };
343
+ }
344
+ const passed = result.status === 0;
345
+ return {
346
+ validator: "pxr.usdchecker",
347
+ status: passed ? "passed" : "failed",
348
+ mode: "pxr-usdchecker",
349
+ semantic_hash: output.semantic_hash,
350
+ primitive_count: output.primitive_count,
351
+ command,
352
+ exitCode: result.status ?? null,
353
+ signal: result.signal ?? null,
354
+ stdout,
355
+ stderr,
356
+ message: passed ? `${command} accepted the emitted USDA stage` : `${command} rejected the emitted USDA stage`
357
+ };
358
+ } catch (error) {
359
+ return {
360
+ validator: "pxr.usdchecker",
361
+ status: "failed",
362
+ mode: "pxr-usdchecker",
363
+ semantic_hash: output.semantic_hash,
364
+ primitive_count: output.primitive_count,
365
+ command,
366
+ message: `Unable to prepare ${command} validation: ${getErrorMessage(error)}`
367
+ };
368
+ } finally {
369
+ if (tempDir) (0, import_fs.rmSync)(tempDir, { recursive: true, force: true });
370
+ }
371
+ }
372
+ function runUsdCheckerProcess(command, args, options) {
373
+ const result = (0, import_child_process.spawnSync)(command, args, {
374
+ encoding: "utf8",
375
+ timeout: options.timeoutMs,
376
+ windowsHide: true
377
+ });
378
+ return {
379
+ status: result.status,
380
+ signal: result.signal,
381
+ stdout: result.stdout,
382
+ stderr: result.stderr,
383
+ error: result.error
384
+ };
385
+ }
386
+ function formatUsdAttribute(key, value, indent) {
387
+ const attrName = sanitizeIdentifier(key);
388
+ if (Array.isArray(value))
389
+ return `${indent}float3 ${attrName} = (${value.map(formatNumber).join(", ")})`;
390
+ if (typeof value === "number") return `${indent}float ${attrName} = ${formatNumber(value)}`;
391
+ if (typeof value === "boolean") return `${indent}bool ${attrName} = ${value ? "true" : "false"}`;
392
+ return `${indent}string ${attrName} = "${escapeUsdString(value)}"`;
393
+ }
394
+ function sanitizeIdentifier(value) {
395
+ const sanitized = value.replace(/\W/g, "_").replace(/^_+/, "");
396
+ const nonEmpty = sanitized.length ? sanitized : "Prim";
397
+ return /^[A-Za-z_]/.test(nonEmpty) ? nonEmpty : `_${nonEmpty}`;
398
+ }
399
+ function escapeUsdString(value) {
400
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
401
+ }
402
+ function unescapeUsdString(value) {
403
+ return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
404
+ }
405
+ function formatNumber(value) {
406
+ if (Number.isInteger(value)) return String(value);
407
+ return String(Number(value.toFixed(6)));
408
+ }
409
+ function normalizeProcessOutput(value) {
410
+ if (value === void 0 || value === null) return void 0;
411
+ const text = String(value).trim();
412
+ return text.length ? text : void 0;
413
+ }
414
+ function getErrorCode(error) {
415
+ if (!error || typeof error !== "object" || !("code" in error)) return void 0;
416
+ const code = error.code;
417
+ return typeof code === "string" ? code : void 0;
418
+ }
419
+ function getErrorMessage(error) {
420
+ if (error instanceof Error) return error.message;
421
+ if (error && typeof error === "object" && "message" in error) {
422
+ const message = error.message;
423
+ if (typeof message === "string") return message;
424
+ }
425
+ return String(error);
426
+ }
427
+ function stableHash(value) {
428
+ const text = stableStringify(value);
429
+ let hash = 2166136261;
430
+ for (let i = 0; i < text.length; i++) {
431
+ hash ^= text.charCodeAt(i);
432
+ hash = Math.imul(hash, 16777619);
433
+ }
434
+ return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
435
+ }
436
+ function stableStringify(value) {
437
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
438
+ if (value && typeof value === "object") {
439
+ const record = value;
440
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
441
+ }
442
+ return JSON.stringify(value);
443
+ }
444
+ // Annotate the CommonJS export names for ESM import in node:
445
+ 0 && (module.exports = {
446
+ buildIndustrialDigitalTwinFixture,
447
+ exportToUsda,
448
+ runOpenUsdConformanceRoundTrip,
449
+ summarizeUsdaRoundTrip,
450
+ usdaStableRoundTrip
451
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,422 @@
1
+ // src/index.ts
2
+ import { spawnSync } from "child_process";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "fs";
4
+ import { tmpdir } from "os";
5
+ import { join } from "path";
6
+ function exportToUsda(input) {
7
+ const lines = [];
8
+ const defaultPrim = sanitizeIdentifier(input.defaultPrim ?? "World");
9
+ const upAxis = input.upAxis ?? "Y";
10
+ const metersPerUnit = input.metersPerUnit ?? 1;
11
+ const stage = input.stage ?? "world";
12
+ const semanticHash = stableHash(input);
13
+ lines.push("#usda 1.0");
14
+ lines.push("(");
15
+ lines.push(` defaultPrim = "${defaultPrim}"`);
16
+ lines.push(` metersPerUnit = ${formatNumber(metersPerUnit)}`);
17
+ lines.push(` upAxis = "${upAxis}"`);
18
+ lines.push(")");
19
+ lines.push("");
20
+ lines.push(`def Xform "${defaultPrim}"`);
21
+ lines.push("{");
22
+ lines.push(` custom string holo:sourceName = "${escapeUsdString(input.name)}"`);
23
+ lines.push(` custom string holo:stage = "${stage}"`);
24
+ lines.push(` custom string holo:semanticHash = "${semanticHash}"`);
25
+ for (const [key, value] of Object.entries(input.customData ?? {})) {
26
+ lines.push(` custom string holo:${sanitizeIdentifier(key)} = "${escapeUsdString(value)}"`);
27
+ }
28
+ const prims = input.primitives ?? [{ kind: "xform", path: "root" }];
29
+ for (const prim of prims) {
30
+ const typeName = prim.kind === "mesh" ? "Mesh" : prim.kind === "light" ? "SphereLight" : "Xform";
31
+ lines.push(` def ${typeName} "${sanitizeIdentifier(prim.path)}"`);
32
+ lines.push(" {");
33
+ lines.push(` custom string holo:sourcePath = "${escapeUsdString(prim.path)}"`);
34
+ if (prim.semantic) {
35
+ pushSemanticReceipt(lines, prim.semantic, " ");
36
+ }
37
+ for (const [k, v] of Object.entries(prim.attrs ?? {})) {
38
+ lines.push(formatUsdAttribute(k, v, " "));
39
+ }
40
+ lines.push(" }");
41
+ }
42
+ lines.push("}");
43
+ lines.push("");
44
+ const usda = lines.join("\n");
45
+ const loc = lines.filter((l) => l.trim().length > 0).length;
46
+ const semanticReceiptCount = prims.filter((p) => p.semantic).length;
47
+ return {
48
+ usda,
49
+ loc,
50
+ primitive_count: input.primitives?.length ?? 1,
51
+ semantic_receipt_count: semanticReceiptCount,
52
+ semantic_hash: semanticHash
53
+ };
54
+ }
55
+ function usdaStableRoundTrip(input) {
56
+ const out = exportToUsda(input);
57
+ const prims = input.primitives ?? [];
58
+ for (const p of prims) {
59
+ const sanitized = sanitizeIdentifier(p.path);
60
+ if (!out.usda.includes(`"${sanitized}"`)) return false;
61
+ }
62
+ return true;
63
+ }
64
+ function summarizeUsdaRoundTrip(usda) {
65
+ const primitiveNames = [...usda.matchAll(/def\s+\w+\s+"([^"]+)"/g)].map((m) => m[1]);
66
+ const semanticSourcePaths = [...usda.matchAll(/custom string holo:sourcePath = "([^"]+)"/g)].map(
67
+ (m) => unescapeUsdString(m[1])
68
+ );
69
+ const semanticRoles = [...usda.matchAll(/custom string holo:role = "([^"]+)"/g)].map(
70
+ (m) => unescapeUsdString(m[1])
71
+ );
72
+ const semanticHash = usda.match(/custom string holo:semanticHash = "([^"]+)"/)?.[1];
73
+ return {
74
+ primitiveNames,
75
+ semanticSourcePaths,
76
+ semanticRoles,
77
+ semanticHash
78
+ };
79
+ }
80
+ function runOpenUsdConformanceRoundTrip(input, options = {}) {
81
+ const output = exportToUsda(input);
82
+ const roundTrip = summarizeUsdaRoundTrip(output.usda);
83
+ const prims = input.primitives ?? [{ kind: "xform", path: "root" }];
84
+ const semanticPrims = prims.filter((p) => p.semantic);
85
+ const checks = [];
86
+ const add = (id, passed, message) => checks.push({ id, passed, message });
87
+ add("magic-header", output.usda.startsWith("#usda 1.0"), "USDA file starts with #usda 1.0");
88
+ add(
89
+ "default-prim",
90
+ output.usda.includes(`defaultPrim = "${sanitizeIdentifier(input.defaultPrim ?? "World")}"`),
91
+ "Default prim is declared in the layer preamble"
92
+ );
93
+ add("up-axis", /upAxis = "[YZ]"/.test(output.usda), "Stage declares a USD upAxis");
94
+ add("meters-per-unit", /metersPerUnit = \d/.test(output.usda), "Stage declares metersPerUnit");
95
+ add(
96
+ "primitive-count",
97
+ output.primitive_count === prims.length,
98
+ "Output primitive_count matches declared primitive count"
99
+ );
100
+ add(
101
+ "primitive-roundtrip",
102
+ prims.every((p) => roundTrip.primitiveNames.includes(sanitizeIdentifier(p.path))),
103
+ "Every declared primitive survives USDA reparse by sanitized name"
104
+ );
105
+ add(
106
+ "semantic-source-paths",
107
+ prims.every((p) => roundTrip.semanticSourcePaths.includes(p.path)),
108
+ "Every primitive sourcePath receipt survives USDA reparse"
109
+ );
110
+ add(
111
+ "semantic-receipts",
112
+ output.semantic_receipt_count === semanticPrims.length && semanticPrims.every((p) => roundTrip.semanticRoles.includes(p.semantic.role)),
113
+ "Every semantic receipt role survives USDA reparse"
114
+ );
115
+ add(
116
+ "semantic-hash",
117
+ roundTrip.semanticHash === output.semantic_hash,
118
+ "Root semantic hash survives USDA reparse"
119
+ );
120
+ const syntaxPassed = checks.every((check) => check.passed);
121
+ const receipts = [
122
+ {
123
+ validator: "syntax-roundtrip",
124
+ status: syntaxPassed ? "passed" : "failed",
125
+ mode: "syntax-roundtrip",
126
+ semantic_hash: output.semantic_hash,
127
+ primitive_count: output.primitive_count,
128
+ message: syntaxPassed ? "Deterministic USDA syntax round-trip passed without requiring pxr bindings" : "Deterministic USDA syntax round-trip failed"
129
+ }
130
+ ];
131
+ const usdcheckerReceipt = runOptionalUsdChecker(output, options.usdchecker);
132
+ if (usdcheckerReceipt) {
133
+ receipts.push(usdcheckerReceipt);
134
+ add("pxr-usdchecker", usdcheckerReceipt.status !== "failed", usdcheckerReceipt.message);
135
+ }
136
+ const validationMode = receipts.some(
137
+ (receipt) => receipt.validator === "pxr.usdchecker" && receipt.status === "passed"
138
+ ) ? "pxr-usdchecker" : "syntax-roundtrip";
139
+ return {
140
+ passed: checks.every((check) => check.passed),
141
+ checks,
142
+ receipts,
143
+ validationMode,
144
+ output,
145
+ roundTrip
146
+ };
147
+ }
148
+ function buildIndustrialDigitalTwinFixture() {
149
+ return {
150
+ name: "HoloScriptFactoryCellTwin",
151
+ stage: "world",
152
+ upAxis: "Z",
153
+ metersPerUnit: 1,
154
+ defaultPrim: "FactoryCell",
155
+ customData: {
156
+ domain: "industrial_digital_twin",
157
+ targetRuntime: "omniverse_openusd",
158
+ evidence: "semantic_receipts_required"
159
+ },
160
+ primitives: [
161
+ {
162
+ kind: "xform",
163
+ path: "/FactoryCell",
164
+ semantic: {
165
+ role: "factory_cell",
166
+ sourcePath: "examples/openusd/industrial-factory-cell.holo",
167
+ dtId: "dtmi:holoscript:factory:cell;1",
168
+ units: "SI",
169
+ simulationContract: "fixed_dt_60hz;z_up;semantic_receipts_required"
170
+ }
171
+ },
172
+ {
173
+ kind: "mesh",
174
+ path: "/FactoryCell/LineA/Conveyor",
175
+ attrs: { position: [0, 0.5, 0], scale: [8, 1, 1.5], material: "conveyor_belt" },
176
+ semantic: {
177
+ role: "conveyor",
178
+ sourcePath: "object:ConveyorLineA",
179
+ dtId: "dt:conveyor:lineA:belt1",
180
+ units: "m/s",
181
+ telemetry: ["speed", "vibration", "temperature"],
182
+ simulationContract: "kinematic_belt;collision_bounds_preserved"
183
+ }
184
+ },
185
+ {
186
+ kind: "mesh",
187
+ path: "/FactoryCell/LineA/MotorM001",
188
+ attrs: { position: [-4.5, 0.8, 0], rpm: 1450, powerKW: 2.2 },
189
+ semantic: {
190
+ role: "motor",
191
+ sourcePath: "object:MotorM001",
192
+ dtId: "dt:motor:lineA:m001",
193
+ units: "rpm,kW",
194
+ telemetry: ["current", "temperature", "vibrationRMS"],
195
+ simulationContract: "predictive_maintenance_receipt_required"
196
+ }
197
+ },
198
+ {
199
+ kind: "xform",
200
+ path: "/FactoryCell/LineA/VibrationSensor",
201
+ attrs: { position: [-2, 1.25, -0.9], samplingHz: 1e3 },
202
+ semantic: {
203
+ role: "sensor",
204
+ sourcePath: "object:VibrationSensorA",
205
+ dtId: "dt:sensor:vibration:lineA:s001",
206
+ units: "mm/s",
207
+ telemetry: ["x", "y", "z"],
208
+ simulationContract: "telemetry_replay_receipt_required"
209
+ }
210
+ },
211
+ {
212
+ kind: "mesh",
213
+ path: "/FactoryCell/Safety/Cage",
214
+ attrs: { position: [0, 1, -3], scale: [10, 2, 0.1], material: "safety_fence" },
215
+ semantic: {
216
+ role: "safety_boundary",
217
+ sourcePath: "object:SafetyCage",
218
+ dtId: "dt:safety:cellA:cage",
219
+ units: "meters",
220
+ simulationContract: "collision_bounds_preserved;interlock_zone"
221
+ }
222
+ },
223
+ {
224
+ kind: "xform",
225
+ path: "/FactoryCell/Assembly/CobotArm",
226
+ attrs: { position: [3, 1.5, -2], payloadKg: 5, reachM: 0.85 },
227
+ semantic: {
228
+ role: "robot_actor",
229
+ sourcePath: "object:CobotArm",
230
+ dtId: "dt:cobot:assembly:cb001",
231
+ units: "kg,m",
232
+ telemetry: ["joint_state", "tool_pose", "safety_stop"],
233
+ simulationContract: "articulation_root;replayable_joint_commands"
234
+ }
235
+ },
236
+ {
237
+ kind: "light",
238
+ path: "/FactoryCell/Inspection/QualityLight",
239
+ attrs: { position: [0, 3, 0], intensity: 6500 },
240
+ semantic: {
241
+ role: "inspection_light",
242
+ sourcePath: "object:QualityLight",
243
+ dtId: "dt:inspection:light:q001",
244
+ units: "lumens",
245
+ simulationContract: "vision_pipeline_lighting_receipt"
246
+ }
247
+ }
248
+ ]
249
+ };
250
+ }
251
+ function pushSemanticReceipt(lines, semantic, indent) {
252
+ lines.push(`${indent}custom string holo:role = "${escapeUsdString(semantic.role)}"`);
253
+ lines.push(
254
+ `${indent}custom string holo:semanticSource = "${escapeUsdString(semantic.sourcePath)}"`
255
+ );
256
+ if (semantic.dtId)
257
+ lines.push(`${indent}custom string holo:dtId = "${escapeUsdString(semantic.dtId)}"`);
258
+ if (semantic.units)
259
+ lines.push(`${indent}custom string holo:units = "${escapeUsdString(semantic.units)}"`);
260
+ if (semantic.telemetry?.length) {
261
+ lines.push(
262
+ `${indent}custom string holo:telemetry = "${escapeUsdString(semantic.telemetry.join(","))}"`
263
+ );
264
+ }
265
+ if (semantic.simulationContract) {
266
+ lines.push(
267
+ `${indent}custom string holo:simulationContract = "${escapeUsdString(semantic.simulationContract)}"`
268
+ );
269
+ }
270
+ if (semantic.provenance) {
271
+ lines.push(
272
+ `${indent}custom string holo:provenance = "${escapeUsdString(semantic.provenance)}"`
273
+ );
274
+ }
275
+ }
276
+ function runOptionalUsdChecker(output, options) {
277
+ if (!options?.enabled) return void 0;
278
+ const command = options.command ?? process.env.HOLOSCRIPT_USDCHECKER ?? "usdchecker";
279
+ const timeoutMs = options.timeoutMs ?? 1e4;
280
+ const runner = options.runner ?? runUsdCheckerProcess;
281
+ let tempDir;
282
+ try {
283
+ tempDir = mkdtempSync(join(tmpdir(), "holoscript-openusd-"));
284
+ const stagePath = join(tempDir, "stage.usda");
285
+ writeFileSync(stagePath, output.usda, "utf8");
286
+ const result = runner(command, [...options.args ?? [], stagePath], { timeoutMs });
287
+ const errorCode = getErrorCode(result.error);
288
+ const stdout = normalizeProcessOutput(result.stdout);
289
+ const stderr = normalizeProcessOutput(result.stderr);
290
+ if (errorCode === "ENOENT") {
291
+ return {
292
+ validator: "pxr.usdchecker",
293
+ status: "unavailable",
294
+ mode: "syntax-roundtrip",
295
+ semantic_hash: output.semantic_hash,
296
+ primitive_count: output.primitive_count,
297
+ command,
298
+ message: `${command} was not found; deterministic syntax round-trip fallback used`
299
+ };
300
+ }
301
+ if (result.error) {
302
+ return {
303
+ validator: "pxr.usdchecker",
304
+ status: "failed",
305
+ mode: "pxr-usdchecker",
306
+ semantic_hash: output.semantic_hash,
307
+ primitive_count: output.primitive_count,
308
+ command,
309
+ exitCode: result.status ?? null,
310
+ signal: result.signal ?? null,
311
+ stdout,
312
+ stderr,
313
+ message: `${command} could not complete: ${getErrorMessage(result.error)}`
314
+ };
315
+ }
316
+ const passed = result.status === 0;
317
+ return {
318
+ validator: "pxr.usdchecker",
319
+ status: passed ? "passed" : "failed",
320
+ mode: "pxr-usdchecker",
321
+ semantic_hash: output.semantic_hash,
322
+ primitive_count: output.primitive_count,
323
+ command,
324
+ exitCode: result.status ?? null,
325
+ signal: result.signal ?? null,
326
+ stdout,
327
+ stderr,
328
+ message: passed ? `${command} accepted the emitted USDA stage` : `${command} rejected the emitted USDA stage`
329
+ };
330
+ } catch (error) {
331
+ return {
332
+ validator: "pxr.usdchecker",
333
+ status: "failed",
334
+ mode: "pxr-usdchecker",
335
+ semantic_hash: output.semantic_hash,
336
+ primitive_count: output.primitive_count,
337
+ command,
338
+ message: `Unable to prepare ${command} validation: ${getErrorMessage(error)}`
339
+ };
340
+ } finally {
341
+ if (tempDir) rmSync(tempDir, { recursive: true, force: true });
342
+ }
343
+ }
344
+ function runUsdCheckerProcess(command, args, options) {
345
+ const result = spawnSync(command, args, {
346
+ encoding: "utf8",
347
+ timeout: options.timeoutMs,
348
+ windowsHide: true
349
+ });
350
+ return {
351
+ status: result.status,
352
+ signal: result.signal,
353
+ stdout: result.stdout,
354
+ stderr: result.stderr,
355
+ error: result.error
356
+ };
357
+ }
358
+ function formatUsdAttribute(key, value, indent) {
359
+ const attrName = sanitizeIdentifier(key);
360
+ if (Array.isArray(value))
361
+ return `${indent}float3 ${attrName} = (${value.map(formatNumber).join(", ")})`;
362
+ if (typeof value === "number") return `${indent}float ${attrName} = ${formatNumber(value)}`;
363
+ if (typeof value === "boolean") return `${indent}bool ${attrName} = ${value ? "true" : "false"}`;
364
+ return `${indent}string ${attrName} = "${escapeUsdString(value)}"`;
365
+ }
366
+ function sanitizeIdentifier(value) {
367
+ const sanitized = value.replace(/\W/g, "_").replace(/^_+/, "");
368
+ const nonEmpty = sanitized.length ? sanitized : "Prim";
369
+ return /^[A-Za-z_]/.test(nonEmpty) ? nonEmpty : `_${nonEmpty}`;
370
+ }
371
+ function escapeUsdString(value) {
372
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
373
+ }
374
+ function unescapeUsdString(value) {
375
+ return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
376
+ }
377
+ function formatNumber(value) {
378
+ if (Number.isInteger(value)) return String(value);
379
+ return String(Number(value.toFixed(6)));
380
+ }
381
+ function normalizeProcessOutput(value) {
382
+ if (value === void 0 || value === null) return void 0;
383
+ const text = String(value).trim();
384
+ return text.length ? text : void 0;
385
+ }
386
+ function getErrorCode(error) {
387
+ if (!error || typeof error !== "object" || !("code" in error)) return void 0;
388
+ const code = error.code;
389
+ return typeof code === "string" ? code : void 0;
390
+ }
391
+ function getErrorMessage(error) {
392
+ if (error instanceof Error) return error.message;
393
+ if (error && typeof error === "object" && "message" in error) {
394
+ const message = error.message;
395
+ if (typeof message === "string") return message;
396
+ }
397
+ return String(error);
398
+ }
399
+ function stableHash(value) {
400
+ const text = stableStringify(value);
401
+ let hash = 2166136261;
402
+ for (let i = 0; i < text.length; i++) {
403
+ hash ^= text.charCodeAt(i);
404
+ hash = Math.imul(hash, 16777619);
405
+ }
406
+ return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
407
+ }
408
+ function stableStringify(value) {
409
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
410
+ if (value && typeof value === "object") {
411
+ const record = value;
412
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
413
+ }
414
+ return JSON.stringify(value);
415
+ }
416
+ export {
417
+ buildIndustrialDigitalTwinFixture,
418
+ exportToUsda,
419
+ runOpenUsdConformanceRoundTrip,
420
+ summarizeUsdaRoundTrip,
421
+ usdaStableRoundTrip
422
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@holoscript/openusd-plugin",
3
+ "version": "0.0.1",
4
+ "description": "OpenUSD interop baseline with USDA export, semantic receipts, and conformance round-trip checks.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsup src/index.ts --format esm,cjs --dts",
9
+ "test": "vitest run",
10
+ "test:coverage": "vitest run --coverage"
11
+ },
12
+ "keywords": [
13
+ "holoscript",
14
+ "plugin",
15
+ "openusd",
16
+ "usda",
17
+ "interop",
18
+ "paper-12"
19
+ ],
20
+ "author": "brianonbased-dev",
21
+ "license": "MIT",
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ]
27
+ }