diff --git a/.gitignore b/.gitignore index 561b1b5..a061144 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ spatialdsl_deploy.key # Claude Code .claude/ CLAUDE.md + +# Benchmark outputs and the local Python environment +benchmarks/results/ +benchmarks/.venv/ diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..23f8052 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,55 @@ +# Evaluation scripts + +Two unattended scripts measure SpatialDSL Studio without a browser, an HTTP +server, or a database. Both call the backend engines in-process and write their +results to `benchmarks/results/` (git-ignored). + +## Prerequisites + +```bash +cd backend && npm ci # the scripts reuse the backend's ts-node and engines +cd ../benchmarks +python3 -m venv .venv && .venv/bin/pip install usd-core # fidelity only; provides pxr +``` + +## Scalability + +```bash +cd benchmarks && npm run scalability +``` + +Generates seeded synthetic Smart Warehouse models with 100, 500, 1,000, 2,500, +5,000, and 10,000 placed elements on a grid with random yaw, about 5 percent of +them placed to violate one of the example spatial rules. For each size it +reports the median of 5 runs (after one warm-up run) of spatial rule +evaluation, the full backend model validation engine, and code generation of +the Smart Warehouse OpenUSD project, plus the serialized model size. The script +fails if the number of rule issues differs from the number of seeded +violations. + +Outputs: `results/scalability.csv`, `results/scalability.md`, +`results/scalability.json` (including CPU, RAM, OS, and Node.js version). +Expected runtime: about 20 seconds on an Apple M2. + +## Generation fidelity + +```bash +cd benchmarks && npm run fidelity # same as: .venv/bin/python fidelity.py [K] [seed] +``` + +Applies K = 200 seeded pose edits (move, rotate, elevate) to the bundled Smart +Warehouse model and regenerates after each edit. Every generated layered USD +stage is opened with pxr; each element's `xformOp:translate`, composed world +translation, `xformOp:rotateZ`, `xformOp:scale`, and `spatialDsl:*` attributes +are compared with the model after the millimetre-to-metre conversion +(tolerance 1e-6). The Visual Components location files and the Omniverse +`warehouse_layout.json` are compared the same way; the layout JSON gives a yaw +only for drones (the other groups are axis-aligned footprints for the +simulator), so their yaw is compared in USD and Visual Components only. The +first and last composed stages are checked with the `UsdValidation` framework, +and the unedited model is generated twice to confirm byte-identical outputs. +The script exits non-zero on any mismatch, nondeterminism, or USD validation +error or warning. + +Outputs: `results/fidelity.json`, `results/fidelity.md`, and the generated +bundles in `results/fidelity/`. Expected runtime: under 10 seconds. diff --git a/benchmarks/fidelity-generate.ts b/benchmarks/fidelity-generate.ts new file mode 100644 index 0000000..44dc6ef --- /dev/null +++ b/benchmarks/fidelity-generate.ts @@ -0,0 +1,140 @@ +/** + * Generation half of the fidelity check (run by fidelity.py). + * + * Applies K seeded pose edits (move, rotate, elevate) to the bundled Smart + * Warehouse model one after another, regenerates the layered OpenUSD bundle + * and the Visual Components location files through the backend generation + * engine after each edit, and writes the outputs plus the expected poses. + * It also generates the unedited model twice and records whether the outputs + * are byte-identical. + * + * npm run fidelity:generate -- [K] [seed] + */ +import './lib/offline-env'; +import fs from 'fs'; +import path from 'path'; +import { CodeGenerationTemplate, Metamodel, Model, ModelElement } from '../shared/types'; +import { codeGenerationEngine } from '../backend/src/services/pipeline/code-generation.engine'; +import { seededRandom } from './lib/synthetic-warehouse'; + +const repoRoot = path.resolve(__dirname, '..'); +const outDir = path.resolve(process.argv[2] || path.join(__dirname, 'results', 'fidelity')); +const editCount = Number(process.argv[3] || 200); +const seed = Number(process.argv[4] || 20261011); + +const readJson = (relativePath: string): any => JSON.parse(fs.readFileSync(path.join(repoRoot, relativePath), 'utf8')); +const metamodel = readJson('frontend/src/examples/data/smart-warehouse-metamodel.json') as Metamodel; +const baseModel = readJson('frontend/src/examples/data/smart-warehouse-model.json') as Model; +const omniverseTemplates = readJson('examples/codegen-projects/smart-warehouse-omniverse-project.json').templates as CodeGenerationTemplate[]; +const visualComponentsTemplates = readJson('examples/codegen-projects/smart-warehouse-visual-components-project.json').templates as CodeGenerationTemplate[]; + +const USD_LAYERS = ['warehouse_layout.usda', 'warehouse_assets.usda', 'warehouse_physics.usda', 'warehouse_simulation.usda']; +const OMNIVERSE_FILES = [...USD_LAYERS, 'warehouse_layout.json']; +const VISUAL_COMPONENTS_FILES = [ + 'PathwayAreaLocation.json', 'RobotLocation.json', 'ConveyorLocation.json', 'OutputLocation.json', 'ChargingStationLocation.json', +]; + +const classNames = new Map(metamodel.classes.map(metaClass => [metaClass.id, metaClass.name])); +const isPlaced = (element: ModelElement): boolean => Boolean(element.presentation?.position3D && element.presentation?.size3D); +const round3 = (value: number): number => Math.round(value * 1000) / 1000; + +const generateAll = (model: Model) => ({ + omniverse: codeGenerationEngine.generate(model, metamodel, omniverseTemplates), + visualComponents: codeGenerationEngine.generate(model, metamodel, visualComponentsTemplates), +}); + +const expectedPoses = (model: Model) => model.elements.filter(isPlaced).map(element => ({ + id: element.id, + name: String(element.style?.name || element.name || element.id), + className: classNames.get(element.modelElementId) || element.modelElementId, + x: element.presentation!.position3D!.x, + y: element.presentation!.position3D!.y, + z: element.presentation!.position3D!.z, + rotationZ: element.presentation!.rotationZ ?? 0, + widthMm: element.presentation!.size3D!.widthMm, + heightMm: element.presentation!.size3D!.heightMm, + depthMm: element.presentation!.size3D!.depthMm, +})); + +const writeOutputs = (directory: string, model: Model, outputs: ReturnType): void => { + fs.mkdirSync(path.join(directory, 'visual-components'), { recursive: true }); + const byName = (files: typeof outputs.omniverse) => new Map(files.map(file => [file.filename, file.content])); + const omniverse = byName(outputs.omniverse); + const visualComponents = byName(outputs.visualComponents); + OMNIVERSE_FILES.forEach(name => fs.writeFileSync(path.join(directory, name), omniverse.get(name)!)); + VISUAL_COMPONENTS_FILES.forEach(name => fs.writeFileSync(path.join(directory, 'visual-components', name), visualComponents.get(name)!)); + // Layers reference assets as @omniverse-assets/...@ relative to the bundle root. + const assetLink = path.join(directory, 'omniverse-assets'); + if (!fs.existsSync(assetLink)) fs.symlinkSync(path.join(repoRoot, 'examples', 'omniverse-assets'), assetLink, 'dir'); + fs.writeFileSync(path.join(directory, 'expected.json'), `${JSON.stringify(expectedPoses(model), null, 2)}\n`); +}; + +fs.rmSync(outDir, { recursive: true, force: true }); +fs.mkdirSync(outDir, { recursive: true }); + +// Determinism: the unedited model generated twice must give byte-identical files. +const first = generateAll(baseModel); +const second = generateAll(JSON.parse(JSON.stringify(baseModel))); +const allFiles = (outputs: ReturnType) => [...outputs.omniverse, ...outputs.visualComponents]; +const differing = allFiles(first) + .filter((file, index) => { + const other = allFiles(second)[index]; + return !other || other.filename !== file.filename || other.content !== file.content || other.contentHash !== file.contentHash; + }) + .map(file => file.filename); +const determinism = { + files: allFiles(first).length, + byteIdentical: differing.length === 0 && allFiles(first).length === allFiles(second).length, + differing, + contentHashes: Object.fromEntries(allFiles(first).map(file => [file.filename, file.contentHash])), +}; + +const random = seededRandom(seed); +const model: Model = JSON.parse(JSON.stringify(baseModel)); +const placed = model.elements.filter(isPlaced); +const edits: Array> = []; +const started = Date.now(); +writeOutputs(path.join(outDir, 'edit-000'), model, first); + +for (let index = 1; index <= editCount; index += 1) { + const element = placed[Math.floor(random() * placed.length)]; + const presentation = element.presentation!; + const before = { ...presentation.position3D!, rotationZ: presentation.rotationZ ?? 0 }; + const roll = random(); + let kind: string; + if (roll < 1 / 3) { + kind = 'move'; + presentation.position3D = { + ...presentation.position3D!, + x: round3(presentation.position3D!.x + (random() * 6000 - 3000)), + y: round3(presentation.position3D!.y + (random() * 6000 - 3000)), + }; + } else if (roll < 2 / 3) { + kind = 'rotate'; + presentation.rotationZ = round3(random() * 360 - 180); + } else { + kind = 'elevate'; + presentation.position3D = { ...presentation.position3D!, z: round3(random() * 3000) }; + } + edits.push({ + index, + kind, + element: element.style?.name, + className: classNames.get(element.modelElementId), + before, + after: { ...presentation.position3D!, rotationZ: presentation.rotationZ ?? 0 }, + }); + writeOutputs(path.join(outDir, `edit-${String(index).padStart(3, '0')}`), model, generateAll(model)); +} + +fs.writeFileSync(path.join(outDir, 'manifest.json'), `${JSON.stringify({ + seed, + edits: editCount, + placedElements: placed.length, + omniverseFiles: OMNIVERSE_FILES, + visualComponentsFiles: VISUAL_COMPONENTS_FILES, + determinism, + generationSeconds: Math.round((Date.now() - started) / 100) / 10, + editLog: edits, +}, null, 2)}\n`); +process.stdout.write(`Generated ${editCount + 1} bundles in ${outDir}; determinism byte-identical: ${determinism.byteIdentical}\n`); diff --git a/benchmarks/fidelity.py b/benchmarks/fidelity.py new file mode 100644 index 0000000..2061072 --- /dev/null +++ b/benchmarks/fidelity.py @@ -0,0 +1,284 @@ +"""Generation fidelity check for the Smart Warehouse example. + +Runs the Node generator (fidelity-generate.ts), which applies K seeded pose +edits to the bundled model and regenerates the layered OpenUSD bundle and the +Visual Components location files after each edit. This script then opens every +generated USD stage with pxr and parses the JSON outputs, compares each +element's pose with the model (millimetres to metres for USD), validates the +composed stage with UsdValidation, and reports the determinism result. + + cd benchmarks && npm run fidelity # or: .venv/bin/python fidelity.py [K] [seed] + +Writes results/fidelity.json and results/fidelity.md. +""" + +import importlib.metadata +import json +import platform +import struct +import subprocess +import sys +import time +from pathlib import Path + +from pxr import Usd, UsdGeom, UsdValidation + +HERE = Path(__file__).resolve().parent +RESULTS = HERE / "results" +OUT = RESULTS / "fidelity" +TOLERANCE = 1e-6 +SCALED_CLASSES = {"Conveyor", "OutputLocation", "ChargingStation", "PathwayArea", "StorageRack", "Dock"} +UNSCALED_CLASSES = {"MobileRobot", "InspectionDrone"} +VC_FILES = { + "PathwayAreaLocation.json": "PathwayArea", + "RobotLocation.json": "MobileRobot", + "ConveyorLocation.json": "Conveyor", + "OutputLocation.json": "OutputLocation", + "ChargingStationLocation.json": "ChargingStation", +} +LAYOUT_JSON_GROUPS = { + "robots": {"MobileRobot"}, + "obstacles": {"StorageRack", "Dock"}, + "pickups": {"Conveyor"}, + "dropoffs": {"OutputLocation"}, + "chargers": {"ChargingStation"}, + "drones": {"InspectionDrone"}, +} +# warehouse_layout.json gives a yaw only for drones; the other groups are axis-aligned +# footprints for the simulator. Every element's yaw is still compared in the USD stage +# (xformOp:rotateZ) and the Visual Components files (Rz). +LAYOUT_JSON_YAW_GROUPS = {"drones"} + + +def float32(value): + """Round to single precision, the declared type of xformOp:rotateZ.""" + return struct.unpack("f", struct.pack("f", float(value)))[0] + + +class Tally: + def __init__(self): + self.checked = 0 + self.mismatches = [] + + def compare(self, target, edit, element, field, actual, expected, tolerance=TOLERANCE): + self.checked += 1 + if actual is None or abs(float(actual) - float(expected)) > tolerance: + self.mismatches.append({ + "target": target, "edit": edit, "element": element, "field": field, + "actual": actual, "expected": expected, + }) + + def missing(self, target, edit, element): + self.checked += 1 + self.mismatches.append({"target": target, "edit": edit, "element": element, "field": "missing"}) + + +def check_usd(edit_dir, edit, expected, tally): + stage = Usd.Stage.Open(str(edit_dir / "warehouse_simulation.usda")) + prims = {} + for prim in stage.Traverse(): + attribute = prim.GetAttribute("spatialDsl:sourceName") + if attribute and attribute.HasAuthoredValue(): + prims[attribute.Get()] = prim + xform_cache = UsdGeom.XformCache() + for element in expected: + name, class_name = element["name"], element["className"] + if class_name not in SCALED_CLASSES | UNSCALED_CLASSES: + continue + prim = prims.get(name) + if prim is None: + tally.missing("usd", edit, name) + continue + x_m, y_m = element["x"] / 1000, element["y"] / 1000 + if class_name in SCALED_CLASSES: + z_m = (element["z"] + element["depthMm"] / 2) / 1000 + else: + z_m = element["z"] / 1000 + translate = prim.GetAttribute("xformOp:translate").Get() + for axis, value in zip("xyz", (x_m, y_m, z_m)): + tally.compare("usd", edit, name, f"translate.{axis}", translate["xyz".index(axis)] if translate else None, value) + # The composed world transform must agree with the layout opinion. + world = xform_cache.GetLocalToWorldTransform(prim).ExtractTranslation() + for axis, value in zip("xyz", (x_m, y_m, z_m)): + tally.compare("usd", edit, name, f"world.{axis}", world["xyz".index(axis)], value) + tally.compare("usd", edit, name, "rotateZ", prim.GetAttribute("xformOp:rotateZ").Get(), float32(element["rotationZ"])) + tally.compare("usd", edit, name, "spatialDsl:baseElevationMm", prim.GetAttribute("spatialDsl:baseElevationMm").Get(), element["z"]) + class_attr = prim.GetAttribute("spatialDsl:className").Get() + tally.checked += 1 + if class_attr != class_name: + tally.mismatches.append({"target": "usd", "edit": edit, "element": name, "field": "className", "actual": class_attr, "expected": class_name}) + if class_name in SCALED_CLASSES: + scale = prim.GetAttribute("xformOp:scale").Get() + for axis, value in zip("xyz", (element["widthMm"], element["heightMm"], element["depthMm"])): + tally.compare("usd", edit, name, f"scale.{axis}", scale["xyz".index(axis)] if scale else None, value / 1000) + return stage + + +def check_visual_components(edit_dir, edit, expected, tally): + for filename, class_name in VC_FILES.items(): + rows = {row["Name"]: row for row in json.loads((edit_dir / "visual-components" / filename).read_text())} + for element in (e for e in expected if e["className"] == class_name): + row = rows.get(element["name"]) + if row is None: + tally.missing("visual-components", edit, element["name"]) + continue + tally.compare("visual-components", edit, element["name"], "X", row["X"], element["x"]) + tally.compare("visual-components", edit, element["name"], "Y", row["Y"], element["y"]) + tally.compare("visual-components", edit, element["name"], "Rz", row["Rz"], element["rotationZ"]) + if class_name == "PathwayArea": + tally.compare("visual-components", edit, element["name"], "AreaLength", row["AreaLength"], element["widthMm"]) + tally.compare("visual-components", edit, element["name"], "AreaWidth", row["AreaWidth"], element["heightMm"]) + + +def check_layout_json(edit_dir, edit, expected, tally): + layout = json.loads((edit_dir / "warehouse_layout.json").read_text()) + for group, classes in LAYOUT_JSON_GROUPS.items(): + rows = {row["name"]: row for row in layout[group]} + for element in (e for e in expected if e["className"] in classes): + row = rows.get(element["name"]) + if row is None: + tally.missing("omniverse-layout-json", edit, element["name"]) + continue + for field, key in (("x", "x"), ("y", "y"), ("z", "z")): + tally.compare("omniverse-layout-json", edit, element["name"], field, row[key], element[key]) + if "length" in row: + tally.compare("omniverse-layout-json", edit, element["name"], "length", row["length"], element["widthMm"]) + tally.compare("omniverse-layout-json", edit, element["name"], "width", row["width"], element["heightMm"]) + tally.compare("omniverse-layout-json", edit, element["name"], "height", row["height"], element["depthMm"]) + if group in LAYOUT_JSON_YAW_GROUPS: + tally.compare("omniverse-layout-json", edit, element["name"], "yaw", row.get("yaw"), element["rotationZ"]) + + +def compliance(path): + """Run the UsdValidation framework on the composed stage. + + Every registered validator runs except the usdz package validators, which + only apply to .usdz archives. + """ + registry = UsdValidation.ValidationRegistry() + names = [meta.name for meta in registry.GetAllValidatorMetadata() + if not meta.isSuite and "UsdzValidators" not in meta.GetKeywords()] + context = UsdValidation.ValidationContext(registry.GetOrLoadValidatorsByName(names)) + findings = context.Validate(Usd.Stage.Open(str(path))) + def describe(finding): + return f"{finding.GetValidator().GetMetadata().name}: {finding.GetMessage()}" + by_type = lambda kind: [describe(f) for f in findings if f.GetType() == kind] + return { + "layer": path.name, + "validators": len(names), + "errors": by_type(UsdValidation.ValidationErrorType.Error), + "failedChecks": [], + "warnings": by_type(UsdValidation.ValidationErrorType.Warn), + "info": by_type(UsdValidation.ValidationErrorType.Info), + } + + +def main(): + edits = int(sys.argv[1]) if len(sys.argv) > 1 else 200 + seed = int(sys.argv[2]) if len(sys.argv) > 2 else 20261011 + started = time.time() + ts_node = HERE.parent / "backend" / "node_modules" / ".bin" / "ts-node" + subprocess.run([str(ts_node), "--transpile-only", "--project", str(HERE.parent / "backend" / "tsconfig.json"), + str(HERE / "fidelity-generate.ts"), str(OUT), str(edits), str(seed)], check=True, cwd=HERE) + generated = time.time() + manifest = json.loads((OUT / "manifest.json").read_text()) + + tally = Tally() + edit_dirs = sorted(p for p in OUT.iterdir() if p.is_dir() and p.name.startswith("edit-")) + for edit_dir in edit_dirs: + edit = int(edit_dir.name.split("-")[1]) + expected = json.loads((edit_dir / "expected.json").read_text()) + check_usd(edit_dir, edit, expected, tally) + check_visual_components(edit_dir, edit, expected, tally) + check_layout_json(edit_dir, edit, expected, tally) + + checks = [compliance(edit_dirs[0] / "warehouse_simulation.usda"), compliance(edit_dirs[-1] / "warehouse_simulation.usda")] + checks[0]["edit"] = edit_dirs[0].name + checks[1]["edit"] = edit_dirs[-1].name + by_target = {} + for item in tally.mismatches: + by_target[item["target"]] = by_target.get(item["target"], 0) + 1 + kinds = {} + for item in manifest["editLog"]: + kinds[item["kind"]] = kinds.get(item["kind"], 0) + 1 + + result = { + "edits": edits, + "editKinds": kinds, + "seed": seed, + "bundles": len(edit_dirs), + "placedElements": manifest["placedElements"], + "valuesChecked": tally.checked, + "mismatches": len(tally.mismatches), + "mismatchesByTarget": by_target, + "mismatchSamples": tally.mismatches[:20], + "tolerance": TOLERANCE, + "determinism": manifest["determinism"], + "compliance": checks, + "usdVersion": importlib.metadata.version("usd-core"), + "python": platform.python_version(), + "platform": f"{platform.system()} {platform.release()} ({platform.machine()})", + "seconds": {"generation": round(generated - started, 1), "checks": round(time.time() - generated, 1)}, + } + RESULTS.mkdir(exist_ok=True) + (RESULTS / "fidelity.json").write_text(json.dumps(result, indent=2) + "\n") + + def compliance_passed(check): + return not check["errors"] and not check["failedChecks"] and not check["warnings"] + + def compliance_line(check): + status = "passed" if compliance_passed(check) else "failed" + return (f"| {check['edit']}/{check['layer']} | {check['validators']} | {status} | {len(check['errors'])} | " + f"{len(check['warnings'])} |") + + lines = [ + "# Generation fidelity results", + "", + f"{edits} seeded pose edits (seed {seed}; {', '.join(f'{v} {k}' for k, v in sorted(kinds.items()))}) applied one after another to the bundled Smart Warehouse model " + f"({manifest['placedElements']} placed elements); outputs regenerated after each edit, {len(edit_dirs)} bundles in total.", + "", + "| Target | Values compared | Mismatches |", + "| --- | ---: | ---: |", + f"| All (tolerance {TOLERANCE}) | {tally.checked} | {len(tally.mismatches)} |", + ] + lines += [f"| {target} | | {count} |" for target, count in sorted(by_target.items())] + lines += [ + "", + "USD values are read from the composed stage `warehouse_simulation.usda` (layout, asset, and physics layers): " + "`xformOp:translate` and the composed world translation in metres, `xformOp:rotateZ` (declared `float`, so the expected " + "yaw is rounded to single precision), `xformOp:scale`, `spatialDsl:baseElevationMm`, and `spatialDsl:className`. " + "Visual Components location files and the Omniverse `warehouse_layout.json` are compared in millimetres and degrees. " + "`warehouse_layout.json` gives a yaw only for drones (the other groups are axis-aligned footprints for the simulator), " + "so the yaw of the other elements is compared in USD and Visual Components only.", + "", + "## USD validation", + "", + "`UsdUtils.ComplianceChecker` is not available in usd-core 26, so the stages are checked with the `UsdValidation` " + "framework instead, running every registered validator except the usdz package ones.", + "", + "| Stage | Validators | Result | Errors | Warnings |", + "| --- | ---: | --- | ---: | ---: |", + *[compliance_line(check) for check in checks], + "", + "## Determinism", + "", + f"Generating the unedited model twice gives byte-identical outputs: **{manifest['determinism']['byteIdentical']}** " + f"({manifest['determinism']['files']} files compared).", + "", + f"Environment: usd-core {result['usdVersion']}, Python {result['python']}, {result['platform']}. " + f"Generation {result['seconds']['generation']} s, checks {result['seconds']['checks']} s.", + "", + ] + for check in checks: + details = check["errors"] + check["warnings"] + check["info"] + if details: + lines += [f"Checker messages for {check['edit']}:", "", *[f"- {message}" for message in details], ""] + (RESULTS / "fidelity.md").write_text("\n".join(lines)) + print("\n".join(lines)) + passed = (not tally.mismatches and manifest["determinism"]["byteIdentical"] + and all(compliance_passed(check) for check in checks)) + return 0 if passed else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/benchmarks/lib/offline-env.ts b/benchmarks/lib/offline-env.ts new file mode 100644 index 0000000..8e4207f --- /dev/null +++ b/benchmarks/lib/offline-env.ts @@ -0,0 +1,7 @@ +// The backend engines import the API middleware, whose config module exits +// when DATABASE_URL is unset. The benchmarks never open a database +// connection, so a placeholder is enough. Import this module first. +process.env.DATABASE_URL = process.env.DATABASE_URL || 'postgresql://benchmarks:unused@localhost:5432/unused'; +process.env.NODE_ENV = process.env.NODE_ENV || 'benchmark'; + +export {}; diff --git a/benchmarks/lib/synthetic-warehouse.ts b/benchmarks/lib/synthetic-warehouse.ts new file mode 100644 index 0000000..d57a820 --- /dev/null +++ b/benchmarks/lib/synthetic-warehouse.ts @@ -0,0 +1,187 @@ +import { Metamodel, Model, ModelElement } from '../../shared/types'; + +/** Deterministic 32-bit PRNG (mulberry32). Same seed, same sequence on every platform. */ +export const seededRandom = (seed: number): (() => number) => { + let state = seed >>> 0; + return () => { + state = (state + 0x6d2b79f5) >>> 0; + let t = state; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +}; + +/** Grid pitch. Larger than the longest rotated footprint, so normal cells never conflict. */ +export const CELL_MM = 8000; + +/** Probability that a cell deliberately violates one rule; about 5 percent of elements end up involved. */ +export const VIOLATION_CELL_PROBABILITY = 0.035; + +type ViolationKind = 'overlap' | 'aisle' | 'stranded-charger' | 'drone-over-rack'; + +export interface SyntheticModel { + model: Model; + violatingElementIds: Set; + violationCells: Record; +} + +interface ElementSpec { + className: string; + size: [number, number, number]; + z?: number; + style?: Record; +} + +const SPECS: Record = { + Conveyor: { className: 'Conveyor', size: [6000, 2000, 3000], style: { InitialPositions: '', Produced: false } }, + StorageRack: { className: 'StorageRack', size: [2000, 1000, 2500], style: { InitialPositions: '', Capacity: 100, Levels: 4 } }, + Dock: { className: 'Dock', size: [5000, 3000, 500], style: { InitialPositions: '', GateNumber: 1 } }, + ChargingStation: { className: 'ChargingStation', size: [2000, 2000, 6000], style: { InitialPositions: '' } }, + PathwayArea: { className: 'PathwayArea', size: [6000, 4000, 200], style: { InitialPositions: '' } }, + OutputLocation: { className: 'OutputLocation', size: [6000, 3000, 3000], style: { InitialPositions: '' } }, + MobileRobot: { + className: 'MobileRobot', + size: [3000, 3000, 4000], + style: { InitialPositions: '', Priority: 1, LowBatteryThreshold: 20, ChargeRate: 10, BatteryLevel: 100, HasProduct: false }, + }, + InspectionDrone: { + className: 'InspectionDrone', + size: [1200, 1200, 400], + z: 4500, + style: { Status: 'Airborne', BatteryLevel: 90, MaxAltitudeMm: 10000 }, + }, +}; + +/** Normal cells, weighted. A charger cell holds a charging station on top of a pathway area. */ +const NORMAL_CELLS: Array<[string, number]> = [ + ['Conveyor', 3], + ['StorageRack', 3], + ['Dock', 1], + ['charger', 1], + ['PathwayArea', 2], + ['OutputLocation', 1], + ['MobileRobot', 2], + ['InspectionDrone', 1], +]; + +const VIOLATIONS: Array<[ViolationKind, number]> = [ + ['overlap', 2], // two conveyors 1000 mm apart: noOverlap among fixed equipment + ['aisle', 2], // two racks with a 200 mm aisle: minimum aisle width + ['stranded-charger', 1], // a charging station with no pathway: adjacency + ['drone-over-rack', 1], // a drone 500 mm above a rack: 3D clearance +]; + +const pickWeighted = (random: () => number, entries: Array<[T, number]>): T => { + const total = entries.reduce((sum, [, weight]) => sum + weight, 0); + let roll = random() * total; + for (const [value, weight] of entries) { + roll -= weight; + if (roll < 0) return value; + } + return entries[entries.length - 1][0]; +}; + +const yaw = (random: () => number): number => Math.round((random() * 360 - 180) * 1000) / 1000; + +/** + * Generate a Smart Warehouse model with exactly `count` placed elements on a + * square grid, each with a random yaw. About 5 percent of the elements sit in + * cells built to violate one of the example spatial rules. + */ +export function generateSyntheticWarehouse(metamodel: Metamodel, count: number, seed: number): SyntheticModel { + const random = seededRandom(seed); + const classIds = Object.fromEntries(metamodel.classes.map(metaClass => [metaClass.name, metaClass.id])); + const elements: ModelElement[] = []; + const violatingElementIds = new Set(); + const violationCells: Record = { + overlap: 0, aisle: 0, 'stranded-charger': 0, 'drone-over-rack': 0, + }; + const chargingStationIds: string[] = []; + const columns = Math.ceil(Math.sqrt(count)); + let cell = 0; + + const add = (spec: ElementSpec, x: number, y: number, rotationZ: number, z = spec.z ?? 0): ModelElement => { + const index = elements.length; + const id = `synthetic-${seed}-${String(index).padStart(6, '0')}`; + const element: ModelElement = { + id, + modelElementId: classIds[spec.className], + style: { name: `${spec.className} ${index}`, ...(spec.style || {}) }, + references: {}, + presentation: { + position3D: { x, y, z }, + size3D: { widthMm: spec.size[0], heightMm: spec.size[1], depthMm: spec.size[2] }, + rotationZ, + }, + }; + if (spec.className === 'ChargingStation') chargingStationIds.push(id); + if (spec.className === 'MobileRobot') { + // assignedStation is mandatory (1..1); the first cell is always a charger cell. + element.references.assignedStation = chargingStationIds[chargingStationIds.length - 1]; + } + elements.push(element); + return element; + }; + + while (elements.length < count) { + const cx = (cell % columns) * CELL_MM; + const cy = Math.floor(cell / columns) * CELL_MM; + const remaining = count - elements.length; + cell += 1; + + if (cell === 1 && remaining >= 2) { + add(SPECS.PathwayArea, cx, cy, yaw(random)); + add(SPECS.ChargingStation, cx, cy, yaw(random)); + continue; + } + + if (remaining >= 2 && random() < VIOLATION_CELL_PROBABILITY) { + const kind = pickWeighted(random, VIOLATIONS); + violationCells[kind] += 1; + const angle = yaw(random); + const radians = (angle * Math.PI) / 180; + const alongY = { x: -Math.sin(radians), y: Math.cos(radians) }; + let created: ModelElement[]; + if (kind === 'overlap') { + created = [ + add(SPECS.Conveyor, cx, cy, angle), + add(SPECS.Conveyor, cx + Math.cos(radians) * 1000, cy + Math.sin(radians) * 1000, angle), + ]; + } else if (kind === 'aisle') { + created = [ + add(SPECS.StorageRack, cx - alongY.x * 600, cy - alongY.y * 600, angle), + add(SPECS.StorageRack, cx + alongY.x * 600, cy + alongY.y * 600, angle), + ]; + } else if (kind === 'stranded-charger') { + created = [add(SPECS.ChargingStation, cx, cy, angle)]; + } else { + created = [add(SPECS.StorageRack, cx, cy, angle), add(SPECS.InspectionDrone, cx, cy, yaw(random), 3000)]; + } + created.forEach(element => violatingElementIds.add(element.id)); + continue; + } + + let kind = pickWeighted(random, NORMAL_CELLS); + if (kind === 'charger' && remaining < 2) kind = 'Conveyor'; + if (kind === 'charger') { + add(SPECS.PathwayArea, cx, cy, yaw(random)); + add(SPECS.ChargingStation, cx, cy, yaw(random)); + } else { + add(SPECS[kind], cx, cy, yaw(random)); + } + } + + return { + model: { + id: `synthetic-warehouse-${count}`, + name: `Synthetic warehouse (${count} elements, seed ${seed})`, + metamodelId: metamodel.id, + conformsTo: metamodel.id, + elements, + connections: [], + }, + violatingElementIds, + violationCells, + }; +} diff --git a/benchmarks/package.json b/benchmarks/package.json new file mode 100644 index 0000000..30ef112 --- /dev/null +++ b/benchmarks/package.json @@ -0,0 +1,10 @@ +{ + "name": "spatialdsl-benchmarks", + "private": true, + "description": "Unattended evaluation scripts for SpatialDSL Studio (scalability and generation fidelity).", + "scripts": { + "scalability": "../backend/node_modules/.bin/ts-node --transpile-only --project ../backend/tsconfig.json scalability.ts", + "fidelity:generate": "../backend/node_modules/.bin/ts-node --transpile-only --project ../backend/tsconfig.json fidelity-generate.ts", + "fidelity": ".venv/bin/python fidelity.py" + } +} diff --git a/benchmarks/scalability.ts b/benchmarks/scalability.ts new file mode 100644 index 0000000..7b59665 --- /dev/null +++ b/benchmarks/scalability.ts @@ -0,0 +1,161 @@ +/** + * Scalability benchmark: spatial rule evaluation, backend model validation, + * and OpenUSD code generation on seeded synthetic Smart Warehouse models. + * + * Runs in-process (no HTTP, no browser, no database): + * cd benchmarks && npm run scalability + * + * Writes benchmarks/results/scalability.csv, scalability.md, and scalability.json. + */ +import './lib/offline-env'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { CodeGenerationTemplate, Metamodel } from '../shared/types'; +import { evaluateSpatialRules } from '../shared/spatial-rules'; +import { modelValidationEngine } from '../backend/src/services/pipeline/model-validation.engine'; +import { codeGenerationEngine } from '../backend/src/services/pipeline/code-generation.engine'; +import { generateSyntheticWarehouse } from './lib/synthetic-warehouse'; + +const SIZES = [100, 500, 1000, 2500, 5000, 10000]; +const RUNS = 5; +const SEED = 20261011; + +const repoRoot = path.resolve(__dirname, '..'); +const resultsDir = path.join(__dirname, 'results'); +const readJson = (relativePath: string): any => JSON.parse(fs.readFileSync(path.join(repoRoot, relativePath), 'utf8')); + +const metamodel = readJson('frontend/src/examples/data/smart-warehouse-metamodel.json') as Metamodel; +const omniverseProject = readJson('examples/codegen-projects/smart-warehouse-omniverse-project.json'); +const templates = omniverseProject.templates as CodeGenerationTemplate[]; + +const median = (values: number[]): number => { + const sorted = [...values].sort((left, right) => left - right); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2; +}; + +const timeMs = (operation: () => T): { result: T; ms: number } => { + const start = process.hrtime.bigint(); + const result = operation(); + return { result, ms: Number(process.hrtime.bigint() - start) / 1e6 }; +}; + +const round = (value: number, digits = 2): number => Math.round(value * 10 ** digits) / 10 ** digits; + +const machine = { + cpu: os.cpus()[0]?.model || 'unknown', + logicalCores: os.cpus().length, + memoryGiB: round(os.totalmem() / 1024 ** 3, 1), + os: `${os.type()} ${os.release()} (${os.platform()}/${os.arch()})`, + node: process.version, + date: new Date().toISOString(), +}; + +interface Row { + elements: number; + violatingElements: number; + seededViolations: number; + spatialIssues: number; + ruleEvaluationMs: number; + validationMs: number; + codeGenerationMs: number; + modelJsonBytes: number; + generatedBytes: number; +} + +const ruleIds = new Set((metamodel.spatialRules || []).map(rule => rule.id)); +const rows: Row[] = []; +const started = Date.now(); + +for (const size of SIZES) { + const { model, violatingElementIds, violationCells } = generateSyntheticWarehouse(metamodel, size, SEED); + // Every seeded violation cell breaks exactly one rule once; normal cells break none. + const seededViolations = Object.values(violationCells).reduce((sum, count) => sum + count, 0); + const ruleTimes: number[] = []; + const validationTimes: number[] = []; + const generationTimes: number[] = []; + let spatialIssues = 0; + let generatedBytes = 0; + + // One warm-up pass (JIT, Handlebars template compilation caches) before the timed runs. + evaluateSpatialRules(metamodel, model); + modelValidationEngine.validate(model, metamodel); + codeGenerationEngine.generate(model, metamodel, templates); + + for (let run = 0; run < RUNS; run += 1) { + const rules = timeMs(() => evaluateSpatialRules(metamodel, model)); + ruleTimes.push(rules.ms); + spatialIssues = rules.result.length; + + const validation = timeMs(() => modelValidationEngine.validate(model, metamodel)); + validationTimes.push(validation.ms); + const ruleIssueCount = validation.result.issues.filter(issue => ruleIds.has(issue.constraintId || '')).length; + if (ruleIssueCount !== spatialIssues) { + throw new Error(`Validation engine reported ${ruleIssueCount} rule issues, evaluator ${spatialIssues}`); + } + + const generation = timeMs(() => codeGenerationEngine.generate(model, metamodel, templates)); + generationTimes.push(generation.ms); + generatedBytes = generation.result.reduce((sum, file) => sum + Buffer.byteLength(file.content), 0); + } + + const row: Row = { + elements: model.elements.length, + violatingElements: violatingElementIds.size, + seededViolations, + spatialIssues, + ruleEvaluationMs: round(median(ruleTimes)), + validationMs: round(median(validationTimes)), + codeGenerationMs: round(median(generationTimes)), + modelJsonBytes: Buffer.byteLength(JSON.stringify(model)), + generatedBytes, + }; + if (row.spatialIssues !== seededViolations) { + throw new Error(`N=${size}: ${row.spatialIssues} rule issues, but ${seededViolations} violations were seeded`); + } + rows.push(row); + process.stdout.write(`N=${row.elements}: rules ${row.ruleEvaluationMs} ms, validation ${row.validationMs} ms, ` + + `codegen ${row.codeGenerationMs} ms, ${row.spatialIssues} rule issues\n`); +} + +fs.mkdirSync(resultsDir, { recursive: true }); +const columns: Array = [ + 'elements', 'violatingElements', 'seededViolations', 'spatialIssues', 'ruleEvaluationMs', + 'validationMs', 'codeGenerationMs', 'modelJsonBytes', 'generatedBytes', +]; +fs.writeFileSync( + path.join(resultsDir, 'scalability.csv'), + `${columns.join(',')}\n${rows.map(row => columns.map(column => row[column]).join(',')).join('\n')}\n` +); +fs.writeFileSync(path.join(resultsDir, 'scalability.json'), `${JSON.stringify({ + machine, seed: SEED, runs: RUNS, sizes: SIZES, rules: metamodel.spatialRules, rows, +}, null, 2)}\n`); + +const kib = (bytes: number): string => `${round(bytes / 1024, 1)}`; +const markdown = [ + '# Scalability results', + '', + `Median of ${RUNS} runs after one warm-up run; seed ${SEED}; ${(metamodel.spatialRules || []).length} spatial rules from the Smart Warehouse example.`, + '', + '| Elements | Violating elements | Seeded violations | Rule issues | Rule evaluation (ms) | Model validation (ms) | OpenUSD generation (ms) | Model JSON (KiB) | Generated (KiB) |', + '| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |', + ...rows.map(row => `| ${row.elements} | ${row.violatingElements} | ${row.seededViolations} | ${row.spatialIssues} | ${row.ruleEvaluationMs} | ` + + `${row.validationMs} | ${row.codeGenerationMs} | ${kib(row.modelJsonBytes)} | ${kib(row.generatedBytes)} |`), + '', + 'Every seeded violation cell (two overlapping conveyors, a 200 mm rack aisle, a charging station off any pathway, or a drone 500 mm above a rack) yields exactly one rule issue, and the script fails otherwise.', + 'Model validation is the full backend engine (conformance, references, presentation, and spatial rules); its timings come from separate runs, so it can be slightly below the rule evaluation median.', + 'OpenUSD generation renders all templates of `examples/codegen-projects/smart-warehouse-omniverse-project.json`.', + '', + '## Machine', + '', + `- CPU: ${machine.cpu} (${machine.logicalCores} logical cores)`, + `- RAM: ${machine.memoryGiB} GiB`, + `- OS: ${machine.os}`, + `- Node.js: ${machine.node}`, + `- Date: ${machine.date}`, + `- Total benchmark wall time: ${round((Date.now() - started) / 1000, 1)} s`, + '', +].join('\n'); +fs.writeFileSync(path.join(resultsDir, 'scalability.md'), markdown); +process.stdout.write(`\n${markdown}`); diff --git a/examples/codegen-projects/smart-warehouse-omniverse-project.json b/examples/codegen-projects/smart-warehouse-omniverse-project.json index 23964f6..4660d1e 100644 --- a/examples/codegen-projects/smart-warehouse-omniverse-project.json +++ b/examples/codegen-projects/smart-warehouse-omniverse-project.json @@ -22,7 +22,7 @@ "id": "a2c1e7d0-0000-4000-8000-0000000000f2", "name": "Generate Layered USD Layout", "language": "plaintext", - "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"SpatialDSL model layout only; asset and physics opinions live in sibling layers.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"layout\"\n\n def Cube \"Floor\"\n {\n float size = 1\n color3f[] primvars:displayColor = [(0.18, 0.20, 0.23)]\n double3 xformOp:scale = (80, 60, 0.1)\n double3 xformOp:translate = (5, 0, -0.05)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n }\n\n def Xform \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"MobileRobot\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n\n def Xform \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Conveyor\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"OutputLocation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"ChargingStation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"PathwayArea\"\n {\n{{#each elementsByClassName.PathwayArea}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"PathwayArea\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"StorageRack\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Dock\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"InspectionDrone\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n}\n", + "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"SpatialDSL model layout only; asset and physics opinions live in sibling layers.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"layout\"\n\n def Cube \"Floor\"\n {\n double size = 1\n color3f[] primvars:displayColor = [(0.18, 0.20, 0.23)]\n double3 xformOp:scale = (80, 60, 0.1)\n double3 xformOp:translate = (5, 0, -0.05)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n }\n\n def Xform \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"MobileRobot\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n\n def Xform \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Conveyor\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"OutputLocation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"ChargingStation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"PathwayArea\"\n {\n{{#each elementsByClassName.PathwayArea}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"PathwayArea\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"StorageRack\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Dock\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"InspectionDrone\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n}\n", "outputPattern": "warehouse_layout.usda" }, { @@ -36,7 +36,7 @@ "id": "a2c1e7d0-0000-4000-8000-0000000000f4", "name": "Generate Layered USD Physics", "language": "plaintext", - "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"PhysX scene, static proxy colliders, and kinematic robot/drone body defaults.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"physics\"\n\n def PhysicsScene \"PhysicsScene\"\n {\n vector3f physics:gravityDirection = (0, 0, -1)\n float physics:gravityMagnitude = 9.81\n }\n\n def Scope \"PhysicsMaterials\"\n {\n def Material \"WarehouseDefault\" (\n prepend apiSchemas = [\"PhysicsMaterialAPI\"]\n )\n {\n float physics:staticFriction = 0.72\n float physics:dynamicFriction = 0.58\n float physics:restitution = 0.05\n }\n }\n\n over \"Floor\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n rel physics:material:binding = \n }\n\n over \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 3.1\n custom string spatialDsl:bodyMode = \"kinematic\"\n custom string spatialDsl:dynamicControl = \"velocity\"\n }\n{{/each}}\n }\n\n over \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 1.5\n custom string spatialDsl:bodyMode = \"kinematic-aerial-placement\"\n\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n double3 xformOp:translate = (0, 0, {{halfMeters Height}})\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n}\n", + "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"PhysX scene, static proxy colliders, and kinematic robot/drone body defaults.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"physics\"\n\n def PhysicsScene \"PhysicsScene\"\n {\n vector3f physics:gravityDirection = (0, 0, -1)\n float physics:gravityMagnitude = 9.81\n }\n\n def Scope \"PhysicsMaterials\"\n {\n def Material \"WarehouseDefault\" (\n prepend apiSchemas = [\"PhysicsMaterialAPI\"]\n )\n {\n float physics:staticFriction = 0.72\n float physics:dynamicFriction = 0.58\n float physics:restitution = 0.05\n }\n }\n\n over \"Floor\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n rel physics:material:binding = \n }\n\n over \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 3.1\n custom string spatialDsl:bodyMode = \"kinematic\"\n custom string spatialDsl:dynamicControl = \"velocity\"\n }\n{{/each}}\n }\n\n over \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 1.5\n custom string spatialDsl:bodyMode = \"kinematic-aerial-placement\"\n\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n double3 xformOp:translate = (0, 0, {{halfMeters Height}})\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n}\n", "outputPattern": "warehouse_physics.usda" }, { diff --git a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/articulation.usda b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/articulation.usda index 00e06ca..c59b543 100644 --- a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/articulation.usda +++ b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/articulation.usda @@ -26,8 +26,8 @@ over "F1TenthAMR" ( uniform token axis = "Z" uniform token purpose = "guide" token visibility = "invisible" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 float physics:mass = 0.12 float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (0, 0.162, 0.058) @@ -41,8 +41,8 @@ over "F1TenthAMR" ( uniform token axis = "Z" uniform token purpose = "guide" token visibility = "invisible" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 float physics:mass = 0.12 float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (0, -0.162, 0.058) diff --git a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/collision.usda b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/collision.usda index aa31b9e..ffeefcd 100644 --- a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/collision.usda +++ b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/collision.usda @@ -15,7 +15,7 @@ over "F1TenthAMR" { uniform token purpose = "guide" token visibility = "invisible" - float size = 1 + double size = 1 custom string spatialDsl:colliderRole = "chassis" double3 xformOp:scale = (0.49, 0.25, 0.15) double3 xformOp:translate = (0, 0, 0.12) @@ -28,7 +28,7 @@ over "F1TenthAMR" { uniform token purpose = "guide" token visibility = "invisible" - float size = 1 + double size = 1 custom string spatialDsl:colliderRole = "sensor-envelope" double3 xformOp:scale = (0.30, 0.18, 0.14) double3 xformOp:translate = (0.015, 0, 0.235) @@ -42,8 +42,8 @@ over "F1TenthAMR" uniform token axis = "Z" uniform token purpose = "guide" token visibility = "invisible" - float height = 0.32 - float radius = 0.057 + double height = 0.32 + double radius = 0.057 custom string spatialDsl:colliderRole = "front-wheel-envelope" float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (0.174, 0, 0.058) @@ -57,8 +57,8 @@ over "F1TenthAMR" uniform token axis = "Z" uniform token purpose = "guide" token visibility = "invisible" - float height = 0.32 - float radius = 0.057 + double height = 0.32 + double radius = 0.057 custom string spatialDsl:colliderRole = "rear-wheel-envelope" float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (-0.174, 0, 0.058) diff --git a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/visual.usda b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/visual.usda index 37763f1..f9111fe 100644 --- a/examples/omniverse-assets/nvidia-f1tenth-amr/layers/visual.usda +++ b/examples/omniverse-assets/nvidia-f1tenth-amr/layers/visual.usda @@ -22,7 +22,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.46, 0.245, 0.012) double3 xformOp:translate = (0, 0, 0.09) @@ -33,7 +33,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.37, 0.225, 0.012) double3 xformOp:translate = (-0.015, 0, 0.178) @@ -44,7 +44,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.045, 0.315, 0.052) double3 xformOp:translate = (0.255, 0, 0.082) @@ -55,7 +55,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.04, 0.29, 0.045) double3 xformOp:translate = (-0.255, 0, 0.082) @@ -66,7 +66,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.19, 0.118, 0.07) double3 xformOp:translate = (-0.06, 0, 0.132) @@ -77,7 +77,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.14, 0.13, 0.055) double3 xformOp:translate = (0.075, 0, 0.218) @@ -88,7 +88,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.33, 0.012, 0.025) double3 xformOp:translate = (-0.01, 0.145, 0.135) @@ -99,7 +99,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.33, 0.012, 0.025) double3 xformOp:translate = (-0.01, -0.145, 0.135) @@ -113,8 +113,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 rel material:binding = float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (0.174, 0.162, 0.058) @@ -125,8 +125,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 rel material:binding = float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (0.174, -0.162, 0.058) @@ -137,8 +137,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 rel material:binding = float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (-0.174, 0.162, 0.058) @@ -149,8 +149,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.052 - float radius = 0.057 + double height = 0.052 + double radius = 0.057 rel material:binding = float3 xformOp:rotateXYZ = (90, 0, 0) double3 xformOp:translate = (-0.174, -0.162, 0.058) @@ -164,7 +164,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.035, 0.255, 0.018) double3 xformOp:translate = (0.174, 0, 0.075) @@ -174,7 +174,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.035, 0.255, 0.018) double3 xformOp:translate = (-0.174, 0, 0.075) @@ -189,8 +189,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.075 - float radius = 0.012 + double height = 0.075 + double radius = 0.012 rel material:binding = double3 xformOp:translate = (-0.085, 0, 0.247) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -200,8 +200,8 @@ def Xform "F1TenthAMR" ( ) { uniform token axis = "Z" - float height = 0.038 - float radius = 0.047 + double height = 0.038 + double radius = 0.047 rel material:binding = double3 xformOp:translate = (-0.085, 0, 0.3) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -210,7 +210,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float size = 1 + double size = 1 rel material:binding = double3 xformOp:scale = (0.035, 0.105, 0.038) double3 xformOp:translate = (0.172, 0, 0.242) @@ -220,7 +220,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float radius = 0.012 + double radius = 0.012 rel material:binding = double3 xformOp:translate = (0.192, 0.033, 0.244) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -229,7 +229,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float radius = 0.012 + double radius = 0.012 rel material:binding = double3 xformOp:translate = (0.192, -0.033, 0.244) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -238,7 +238,7 @@ def Xform "F1TenthAMR" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { - float radius = 0.014 + double radius = 0.014 rel material:binding = double3 xformOp:translate = (-0.165, 0, 0.225) uniform token[] xformOpOrder = ["xformOp:translate"] diff --git a/examples/omniverse-assets/warehouse-kit/inspection_drone.usda b/examples/omniverse-assets/warehouse-kit/inspection_drone.usda index ffd6b9c..e2b5903 100644 --- a/examples/omniverse-assets/warehouse-kit/inspection_drone.usda +++ b/examples/omniverse-assets/warehouse-kit/inspection_drone.usda @@ -14,7 +14,7 @@ def Xform "InspectionDrone" def Cube "Body" { - float size = 1 + double size = 1 color3f[] primvars:displayColor = [(0.34, 0.16, 0.62)] double3 xformOp:scale = (0.55, 0.34, 0.20) double3 xformOp:translate = (0, 0, 0.20) @@ -23,7 +23,7 @@ def Xform "InspectionDrone" def Cube "ArmX" { - float size = 1 + double size = 1 color3f[] primvars:displayColor = [(0.55, 0.38, 0.82)] double3 xformOp:scale = (1.0, 0.08, 0.08) double3 xformOp:translate = (0, 0, 0.23) @@ -32,7 +32,7 @@ def Xform "InspectionDrone" def Cube "ArmY" { - float size = 1 + double size = 1 color3f[] primvars:displayColor = [(0.55, 0.38, 0.82)] double3 xformOp:scale = (0.08, 1.0, 0.08) double3 xformOp:translate = (0, 0, 0.23) @@ -41,7 +41,7 @@ def Xform "InspectionDrone" def Cube "LandingSkidLeft" { - float size = 1 + double size = 1 color3f[] primvars:displayColor = [(0.16, 0.18, 0.24)] double3 xformOp:scale = (0.55, 0.05, 0.08) double3 xformOp:translate = (0, -0.27, 0.04) @@ -50,7 +50,7 @@ def Xform "InspectionDrone" def Cube "LandingSkidRight" { - float size = 1 + double size = 1 color3f[] primvars:displayColor = [(0.16, 0.18, 0.24)] double3 xformOp:scale = (0.55, 0.05, 0.08) double3 xformOp:translate = (0, 0.27, 0.04) @@ -60,8 +60,8 @@ def Xform "InspectionDrone" def Cylinder "RotorFront" { uniform token axis = "Z" - float height = 0.025 - float radius = 0.20 + double height = 0.025 + double radius = 0.20 color3f[] primvars:displayColor = [(0.10, 0.12, 0.18)] double3 xformOp:translate = (0.50, 0, 0.31) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -70,8 +70,8 @@ def Xform "InspectionDrone" def Cylinder "RotorBack" { uniform token axis = "Z" - float height = 0.025 - float radius = 0.20 + double height = 0.025 + double radius = 0.20 color3f[] primvars:displayColor = [(0.10, 0.12, 0.18)] double3 xformOp:translate = (-0.50, 0, 0.31) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -80,8 +80,8 @@ def Xform "InspectionDrone" def Cylinder "RotorLeft" { uniform token axis = "Z" - float height = 0.025 - float radius = 0.20 + double height = 0.025 + double radius = 0.20 color3f[] primvars:displayColor = [(0.10, 0.12, 0.18)] double3 xformOp:translate = (0, -0.50, 0.31) uniform token[] xformOpOrder = ["xformOp:translate"] @@ -90,8 +90,8 @@ def Xform "InspectionDrone" def Cylinder "RotorRight" { uniform token axis = "Z" - float height = 0.025 - float radius = 0.20 + double height = 0.025 + double radius = 0.20 color3f[] primvars:displayColor = [(0.10, 0.12, 0.18)] double3 xformOp:translate = (0, 0.50, 0.31) uniform token[] xformOpOrder = ["xformOp:translate"] diff --git a/frontend/src/examples/data/smart-warehouse-omniverse-project.json b/frontend/src/examples/data/smart-warehouse-omniverse-project.json index 23964f6..4660d1e 100644 --- a/frontend/src/examples/data/smart-warehouse-omniverse-project.json +++ b/frontend/src/examples/data/smart-warehouse-omniverse-project.json @@ -22,7 +22,7 @@ "id": "a2c1e7d0-0000-4000-8000-0000000000f2", "name": "Generate Layered USD Layout", "language": "plaintext", - "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"SpatialDSL model layout only; asset and physics opinions live in sibling layers.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"layout\"\n\n def Cube \"Floor\"\n {\n float size = 1\n color3f[] primvars:displayColor = [(0.18, 0.20, 0.23)]\n double3 xformOp:scale = (80, 60, 0.1)\n double3 xformOp:translate = (5, 0, -0.05)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n }\n\n def Xform \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"MobileRobot\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n\n def Xform \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Conveyor\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"OutputLocation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"ChargingStation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"PathwayArea\"\n {\n{{#each elementsByClassName.PathwayArea}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"PathwayArea\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"StorageRack\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Dock\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"InspectionDrone\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n}\n", + "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"SpatialDSL model layout only; asset and physics opinions live in sibling layers.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"layout\"\n\n def Cube \"Floor\"\n {\n double size = 1\n color3f[] primvars:displayColor = [(0.18, 0.20, 0.23)]\n double3 xformOp:scale = (80, 60, 0.1)\n double3 xformOp:translate = (5, 0, -0.05)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n }\n\n def Xform \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"MobileRobot\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n\n def Xform \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Conveyor\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"OutputLocation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"ChargingStation\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"PathwayArea\"\n {\n{{#each elementsByClassName.PathwayArea}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"PathwayArea\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"StorageRack\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"Dock\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{baseCenterMeters Z Height}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\", \"xformOp:scale\"]\n }\n{{/each}}\n }\n\n def Xform \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n def Xform \"{{usdIdentifier name}}_{{@index}}\"\n {\n custom string spatialDsl:className = \"InspectionDrone\"\n custom string spatialDsl:sourceName = \"{{name}}\"\n custom double spatialDsl:baseElevationMm = {{Z}}\n double3 xformOp:translate = ({{meters X}}, {{meters Y}}, {{meters Z}})\n float xformOp:rotateZ = {{#unless RZ}}0{{else}}{{RZ}}{{/unless}}\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateZ\"]\n }\n{{/each}}\n }\n}\n", "outputPattern": "warehouse_layout.usda" }, { @@ -36,7 +36,7 @@ "id": "a2c1e7d0-0000-4000-8000-0000000000f4", "name": "Generate Layered USD Physics", "language": "plaintext", - "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"PhysX scene, static proxy colliders, and kinematic robot/drone body defaults.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"physics\"\n\n def PhysicsScene \"PhysicsScene\"\n {\n vector3f physics:gravityDirection = (0, 0, -1)\n float physics:gravityMagnitude = 9.81\n }\n\n def Scope \"PhysicsMaterials\"\n {\n def Material \"WarehouseDefault\" (\n prepend apiSchemas = [\"PhysicsMaterialAPI\"]\n )\n {\n float physics:staticFriction = 0.72\n float physics:dynamicFriction = 0.58\n float physics:restitution = 0.05\n }\n }\n\n over \"Floor\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n rel physics:material:binding = \n }\n\n over \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 3.1\n custom string spatialDsl:bodyMode = \"kinematic\"\n custom string spatialDsl:dynamicControl = \"velocity\"\n }\n{{/each}}\n }\n\n over \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 1.5\n custom string spatialDsl:bodyMode = \"kinematic-aerial-placement\"\n\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n float size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n double3 xformOp:translate = (0, 0, {{halfMeters Height}})\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n}\n", + "templateContent": "#usda 1.0\n(\n defaultPrim = \"World\"\n documentation = \"PhysX scene, static proxy colliders, and kinematic robot/drone body defaults.\"\n metersPerUnit = 1\n timeCodesPerSecond = 60\n upAxis = \"Z\"\n)\n\ndef Xform \"World\"\n{\n custom string spatialDsl:layerRole = \"physics\"\n\n def PhysicsScene \"PhysicsScene\"\n {\n vector3f physics:gravityDirection = (0, 0, -1)\n float physics:gravityMagnitude = 9.81\n }\n\n def Scope \"PhysicsMaterials\"\n {\n def Material \"WarehouseDefault\" (\n prepend apiSchemas = [\"PhysicsMaterialAPI\"]\n )\n {\n float physics:staticFriction = 0.72\n float physics:dynamicFriction = 0.58\n float physics:restitution = 0.05\n }\n }\n\n over \"Floor\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n rel physics:material:binding = \n }\n\n over \"MobileRobot\"\n {\n{{#each elementsByClassName.MobileRobot}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 3.1\n custom string spatialDsl:bodyMode = \"kinematic\"\n custom string spatialDsl:dynamicControl = \"velocity\"\n }\n{{/each}}\n }\n\n over \"Conveyor\"\n {\n{{#each elementsByClassName.Conveyor}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"OutputLocation\"\n {\n{{#each elementsByClassName.OutputLocation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"ChargingStation\"\n {\n{{#each elementsByClassName.ChargingStation}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"StorageRack\"\n {\n{{#each elementsByClassName.StorageRack}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"Dock\"\n {\n{{#each elementsByClassName.Dock}}\n over \"{{usdIdentifier name}}_{{@index}}\"\n {\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n\n over \"InspectionDrone\"\n {\n{{#each elementsByClassName.InspectionDrone}}\n over \"{{usdIdentifier name}}_{{@index}}\" (\n prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsMassAPI\"]\n )\n {\n bool physics:kinematicEnabled = true\n bool physics:rigidBodyEnabled = true\n float physics:mass = 1.5\n custom string spatialDsl:bodyMode = \"kinematic-aerial-placement\"\n\n def Cube \"PhysicsProxy\" (\n prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n )\n {\n double size = 1\n uniform token purpose = \"guide\"\n token visibility = \"invisible\"\n double3 xformOp:translate = (0, 0, {{halfMeters Height}})\n double3 xformOp:scale = ({{meters Length}}, {{meters Width}}, {{meters Height}})\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:scale\"]\n rel physics:material:binding = \n }\n }\n{{/each}}\n }\n}\n", "outputPattern": "warehouse_physics.usda" }, {