From bed678fa950b5ab5ddf307fa983ba909bf4bcedd Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Thu, 14 May 2026 06:59:58 +0300 Subject: [PATCH 01/43] feat: include dimsim Co-authored-by: Viswajit Nair --- .gitattributes | 3 + .github/workflows/dimsim-check.yml | 58 + .gitignore | 4 +- dimos/simulation/dimsim/dimsim_process.py | 29 +- misc/DimSim/.gitignore | 7 + misc/DimSim/README.md | 55 + misc/DimSim/deno.lock | 122 + misc/DimSim/dimos-cli/README.md | 58 + misc/DimSim/dimos-cli/agent.py | 104 + misc/DimSim/dimos-cli/bridge/lidar.ts | 284 + misc/DimSim/dimos-cli/bridge/physics.ts | 385 + misc/DimSim/dimos-cli/bridge/server.ts | 349 + misc/DimSim/dimos-cli/cli.ts | 873 ++ misc/DimSim/dimos-cli/deno.json | 37 + misc/DimSim/dimos-cli/deno.lock | 117 + misc/DimSim/dimos-cli/eval/builder.ts | 123 + misc/DimSim/dimos-cli/eval/runner.ts | 389 + misc/DimSim/dimos-cli/eval/scene-index.ts | 107 + misc/DimSim/dimos-cli/headless/launcher.ts | 204 + misc/DimSim/dimos-cli/mod.ts | 86 + misc/DimSim/dimos-cli/run-eval.ts | 40 + misc/DimSim/dimos-cli/setup.ts | 357 + .../DimSim/dimos-cli/test/diagnose_costmap.py | 196 + .../dimos-cli/test/dimos_integration.py | 245 + misc/DimSim/dimos-cli/test/lcm_cross_test.py | 46 + misc/DimSim/dimos-cli/test/lcm_cross_test.ts | 37 + misc/DimSim/dimos-cli/test/loopback.ts | 120 + misc/DimSim/dimos-cli/test/rubrics_test.ts | 164 + .../dimos-cli/test/scene_editor_test.py | 498 ++ misc/DimSim/dimos-cli/test/smoke.ts | 75 + misc/DimSim/dimos-cli/vendor/lcm/lcm.ts | 236 + misc/DimSim/dimos-cli/vendor/lcm/mod.ts | 6 + misc/DimSim/dimos-cli/vendor/lcm/transport.ts | 352 + misc/DimSim/dimos-cli/vendor/lcm/types.ts | 62 + misc/DimSim/dimos-cli/vendor/lcm/url.ts | 61 + misc/DimSim/docker/ci-test/Dockerfile | 37 + misc/DimSim/docker/ci-test/build-and-test.sh | 20 + misc/DimSim/docker/ci-test/run-test.sh | 82 + misc/DimSim/docker/cli-test/Dockerfile | 23 + misc/DimSim/docker/cli-test/test.sh | 80 + misc/DimSim/evals/apt/go-to-couch.json | 19 + misc/DimSim/evals/apt/go-to-kitchen.json | 19 + misc/DimSim/evals/apt/go-to-tv.json | 19 + misc/DimSim/evals/apt/television.json | 19 + misc/DimSim/evals/manifest.json | 15 + misc/DimSim/index.html | 212 + misc/DimSim/package-lock.json | 2120 +++++ misc/DimSim/package.json | 30 + misc/DimSim/public/agent-model/robot.glb | 3 + misc/DimSim/public/logo.svg | 13 + misc/DimSim/public/sims/apt.json | 3 + misc/DimSim/public/sims/apt_.json | 3 + misc/DimSim/public/sims/empty.json | 3 + misc/DimSim/public/sims/manifest.json | 3 + misc/DimSim/scenes.template.json | 9 + misc/DimSim/scripts/package-release.sh | 41 + misc/DimSim/scripts/profile-live.sh | 56 + misc/DimSim/scripts/speed-test.py | 221 + misc/DimSim/server.js | 174 + misc/DimSim/src/AiAvatar.js | 1507 ++++ misc/DimSim/src/ai/modelConfig.js | 14 + misc/DimSim/src/ai/sim/vlmActions.js | 109 + misc/DimSim/src/ai/sim/vlmPrompt.js | 80 + misc/DimSim/src/ai/visionCapture.js | 267 + misc/DimSim/src/ai/vlmClient.js | 36 + misc/DimSim/src/dimos/dimosBridge.ts | 557 ++ misc/DimSim/src/dimos/evalHarness.ts | 227 + misc/DimSim/src/dimos/rubrics.ts | 156 + misc/DimSim/src/dimos/sceneEditor.ts | 452 + misc/DimSim/src/engine.js | 7763 +++++++++++++++++ misc/DimSim/src/main.js | 4 + misc/DimSim/src/style.css | 3188 +++++++ misc/DimSim/update-sims.sh | 16 + misc/DimSim/vite.config.js | 22 + misc/DimSim/vlm-server/asset-library.json | 1 + pyproject.toml | 3 + 76 files changed, 23486 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/dimsim-check.yml create mode 100644 misc/DimSim/.gitignore create mode 100644 misc/DimSim/README.md create mode 100644 misc/DimSim/deno.lock create mode 100644 misc/DimSim/dimos-cli/README.md create mode 100644 misc/DimSim/dimos-cli/agent.py create mode 100644 misc/DimSim/dimos-cli/bridge/lidar.ts create mode 100644 misc/DimSim/dimos-cli/bridge/physics.ts create mode 100644 misc/DimSim/dimos-cli/bridge/server.ts create mode 100644 misc/DimSim/dimos-cli/cli.ts create mode 100644 misc/DimSim/dimos-cli/deno.json create mode 100644 misc/DimSim/dimos-cli/deno.lock create mode 100644 misc/DimSim/dimos-cli/eval/builder.ts create mode 100644 misc/DimSim/dimos-cli/eval/runner.ts create mode 100644 misc/DimSim/dimos-cli/eval/scene-index.ts create mode 100644 misc/DimSim/dimos-cli/headless/launcher.ts create mode 100644 misc/DimSim/dimos-cli/mod.ts create mode 100644 misc/DimSim/dimos-cli/run-eval.ts create mode 100644 misc/DimSim/dimos-cli/setup.ts create mode 100644 misc/DimSim/dimos-cli/test/diagnose_costmap.py create mode 100755 misc/DimSim/dimos-cli/test/dimos_integration.py create mode 100644 misc/DimSim/dimos-cli/test/lcm_cross_test.py create mode 100644 misc/DimSim/dimos-cli/test/lcm_cross_test.ts create mode 100644 misc/DimSim/dimos-cli/test/loopback.ts create mode 100644 misc/DimSim/dimos-cli/test/rubrics_test.ts create mode 100644 misc/DimSim/dimos-cli/test/scene_editor_test.py create mode 100644 misc/DimSim/dimos-cli/test/smoke.ts create mode 100644 misc/DimSim/dimos-cli/vendor/lcm/lcm.ts create mode 100644 misc/DimSim/dimos-cli/vendor/lcm/mod.ts create mode 100644 misc/DimSim/dimos-cli/vendor/lcm/transport.ts create mode 100644 misc/DimSim/dimos-cli/vendor/lcm/types.ts create mode 100644 misc/DimSim/dimos-cli/vendor/lcm/url.ts create mode 100644 misc/DimSim/docker/ci-test/Dockerfile create mode 100755 misc/DimSim/docker/ci-test/build-and-test.sh create mode 100755 misc/DimSim/docker/ci-test/run-test.sh create mode 100644 misc/DimSim/docker/cli-test/Dockerfile create mode 100755 misc/DimSim/docker/cli-test/test.sh create mode 100644 misc/DimSim/evals/apt/go-to-couch.json create mode 100644 misc/DimSim/evals/apt/go-to-kitchen.json create mode 100644 misc/DimSim/evals/apt/go-to-tv.json create mode 100644 misc/DimSim/evals/apt/television.json create mode 100644 misc/DimSim/evals/manifest.json create mode 100644 misc/DimSim/index.html create mode 100644 misc/DimSim/package-lock.json create mode 100644 misc/DimSim/package.json create mode 100644 misc/DimSim/public/agent-model/robot.glb create mode 100644 misc/DimSim/public/logo.svg create mode 100644 misc/DimSim/public/sims/apt.json create mode 100644 misc/DimSim/public/sims/apt_.json create mode 100644 misc/DimSim/public/sims/empty.json create mode 100644 misc/DimSim/public/sims/manifest.json create mode 100644 misc/DimSim/scenes.template.json create mode 100755 misc/DimSim/scripts/package-release.sh create mode 100755 misc/DimSim/scripts/profile-live.sh create mode 100644 misc/DimSim/scripts/speed-test.py create mode 100644 misc/DimSim/server.js create mode 100644 misc/DimSim/src/AiAvatar.js create mode 100644 misc/DimSim/src/ai/modelConfig.js create mode 100644 misc/DimSim/src/ai/sim/vlmActions.js create mode 100644 misc/DimSim/src/ai/sim/vlmPrompt.js create mode 100644 misc/DimSim/src/ai/visionCapture.js create mode 100644 misc/DimSim/src/ai/vlmClient.js create mode 100644 misc/DimSim/src/dimos/dimosBridge.ts create mode 100644 misc/DimSim/src/dimos/evalHarness.ts create mode 100644 misc/DimSim/src/dimos/rubrics.ts create mode 100644 misc/DimSim/src/dimos/sceneEditor.ts create mode 100644 misc/DimSim/src/engine.js create mode 100644 misc/DimSim/src/main.js create mode 100644 misc/DimSim/src/style.css create mode 100644 misc/DimSim/update-sims.sh create mode 100644 misc/DimSim/vite.config.js create mode 100644 misc/DimSim/vlm-server/asset-library.json diff --git a/.gitattributes b/.gitattributes index c4ccd1f825..b4ea3b5a3b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,3 +18,6 @@ *.foxe filter=lfs diff=lfs merge=lfs -text binary docs/capabilities/memory/assets/** filter=lfs diff=lfs merge=lfs -text docs/capabilities/memory/assets/.gitattributes -filter -diff -merge text +# DimSim scene data and agent model +misc/DimSim/public/sims/*.json filter=lfs diff=lfs merge=lfs -text +misc/DimSim/public/agent-model/*.glb filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/dimsim-check.yml b/.github/workflows/dimsim-check.yml new file mode 100644 index 0000000000..372f919db0 --- /dev/null +++ b/.github/workflows/dimsim-check.yml @@ -0,0 +1,58 @@ +name: dimsim-check + +on: + push: + branches: [main] + paths: + - 'misc/DimSim/**' + - '.github/workflows/dimsim-check.yml' + pull_request: + paths: + - 'misc/DimSim/**' + - '.github/workflows/dimsim-check.yml' + +permissions: {} + +jobs: + check: + timeout-minutes: 15 + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: misc/DimSim + steps: + - uses: actions/checkout@v6 + with: + lfs: true + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Install npm deps + run: npm ci + + - name: Build frontend + run: npm run build + + - name: Type-check CLI + run: cd dimos-cli && deno check cli.ts mod.ts setup.ts + + - name: Validate scenes template + run: | + for scene in $(jq -r '.scenes | keys[]' scenes.template.json); do + DESC=$(jq -r ".scenes.\"$scene\".description" scenes.template.json) + SIZE=$(jq -r ".scenes.\"$scene\".size" scenes.template.json) + if [ "$DESC" = "null" ] || [ "$SIZE" = "null" ]; then + echo "::error::Scene '$scene' missing description or size in scenes.template.json" + exit 1 + fi + echo " $scene: $DESC (${SIZE} bytes)" + done + echo "Template valid." diff --git a/.gitignore b/.gitignore index 7e12f67569..efb34af7d0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,8 @@ __pycache__ # node env (used by devcontainers cli) node_modules -package.json -package-lock.json +/package.json +/package-lock.json # Ignore build artifacts dist/ diff --git a/dimos/simulation/dimsim/dimsim_process.py b/dimos/simulation/dimsim/dimsim_process.py index ead774c677..67803dcf89 100644 --- a/dimos/simulation/dimsim/dimsim_process.py +++ b/dimos/simulation/dimsim/dimsim_process.py @@ -19,7 +19,6 @@ import time from typing import IO -from dimos.constants import STATE_DIR from dimos.core.global_config import GlobalConfig from dimos.simulation.dimsim.deno_utils import ensure_deno, ensure_playwright_chromium from dimos.utils.logging_config import setup_logger @@ -28,8 +27,7 @@ _VIDEO_RATE = 50 _LIDAR_RATE = 1000 -_DIMSIM_REPO_URL = "https://github.com/paul-nechifor/DimSim.git" -_DIMSIM_REPO_BRANCH = "run-from-repo" +_DIMSIM_DIR = Path(__file__).resolve().parents[3] / "misc" / "DimSim" class DimSimProcess: @@ -39,8 +37,7 @@ def __init__(self, global_config: GlobalConfig) -> None: def start(self) -> None: deno_path = ensure_deno() - repo_dir = _ensure_repo() - base_cmd = _deno_cmd(deno_path, repo_dir) + base_cmd = _deno_cmd(deno_path, _DIMSIM_DIR) scene = self.global_config.dimsim_scene port = self.global_config.dimsim_port @@ -126,28 +123,6 @@ def _kill_port_holder(port: int) -> None: logger.warning(f"Failed to check/kill port {port}: {e}") -def _ensure_repo() -> Path: - repo_dir = STATE_DIR / "dimsim_repo" - if (repo_dir / ".git").exists(): - return repo_dir - STATE_DIR.mkdir(parents=True, exist_ok=True) - logger.info(f"Cloning DimSim into {repo_dir}") - subprocess.run( - [ - "git", - "clone", - "--depth", - "1", - "--branch", - _DIMSIM_REPO_BRANCH, - _DIMSIM_REPO_URL, - str(repo_dir), - ], - check=True, - ) - return repo_dir - - def _deno_cmd(deno_path: str, repo_dir: Path) -> list[str]: cli_ts = repo_dir / "dimos-cli" / "cli.ts" return [deno_path, "run", "--allow-all", "--unstable-net", str(cli_ts)] diff --git a/misc/DimSim/.gitignore b/misc/DimSim/.gitignore new file mode 100644 index 0000000000..ce85a96daa --- /dev/null +++ b/misc/DimSim/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +*.tar.gz +dist/ +.DS_Store +**/.DS_Store +.deno/ +scenes.json diff --git a/misc/DimSim/README.md b/misc/DimSim/README.md new file mode 100644 index 0000000000..4df77dd82e --- /dev/null +++ b/misc/DimSim/README.md @@ -0,0 +1,55 @@ +# DimSim + +Standalone 3D simulation runner for SimStudio scenes. Load a scene, spawn AI agents, run tasks — with full sensor support (RGB-D, LiDAR). + +## Setup + +```bash +npm install # installs everything (frontend + backend) +``` + +## Run + +Terminal 1: +```bash +npm run server # Node.js VLM backend on :8000 +``` + +Terminal 2: +```bash +npm run dev # Frontend on :5173 +``` + +## Architecture + +``` +DimSim/ +├── index.html ← Sim-mode UI (scene dropdown + full sensor controls) +├── server.js ← VLM backend (Express + OpenAI SDK) +├── src/ +│ ├── main.js ← Entry point (imports engine.js) +│ ├── engine.js ← Full SimStudio engine (synced via copy-sources.sh) +│ ├── style.css ← Synced from SimStudio +│ ├── AiAvatar.js ← Agent class (synced) +│ └── ai/ ← VLM modules (synced) +├── public/ +│ ├── sims/ ← Scene JSON files + manifest.json +│ └── agent-model/ ← Robot GLB models +├── vlm-server/ +│ └── asset-library.json ← Persisted asset library data +├── copy-sources.sh ← Sync engine from SimStudio +└── update-sims.sh ← Rebuild scene manifest +``` + +## Sync from SimStudio + +```bash +npm run sync +``` + +## Add/remove scenes + +Drop `.json` files in `public/sims/`, then: +```bash +npm run update-sims +``` diff --git a/misc/DimSim/deno.lock b/misc/DimSim/deno.lock new file mode 100644 index 0000000000..d1b33f132e --- /dev/null +++ b/misc/DimSim/deno.lock @@ -0,0 +1,122 @@ +{ + "version": "5", + "specifiers": { + "jsr:@antim/dimsim@*": "0.1.3", + "jsr:@antim/dimsim@0.1.28": "0.1.28", + "jsr:@antim/dimsim@0.1.29": "0.1.29", + "jsr:@dimos/lcm@*": "0.2.0", + "jsr:@dimos/lcm@0.2.0": "0.2.0", + "jsr:@dimos/msgs@0.1.4": "0.1.4", + "jsr:@dimos/msgs@~0.1.4": "0.1.4", + "jsr:@std/assert@*": "1.0.19", + "jsr:@std/cli@^1.0.28": "1.0.28", + "jsr:@std/encoding@^1.0.10": "1.0.10", + "jsr:@std/fmt@^1.0.9": "1.0.9", + "jsr:@std/fs@^1.0.23": "1.0.23", + "jsr:@std/html@^1.0.5": "1.0.5", + "jsr:@std/http@1": "1.0.25", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/media-types@^1.1.0": "1.1.0", + "jsr:@std/net@^1.0.6": "1.0.6", + "jsr:@std/path@1": "1.1.4", + "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/streams@^1.0.17": "1.0.17" + }, + "jsr": { + "@antim/dimsim@0.1.3": { + "integrity": "b145e83a4545ba03004ccfcd0c14afad3cb9af3258f8174b0f0a8cc64b9949da", + "dependencies": [ + "jsr:@std/http", + "jsr:@std/path@1" + ] + }, + "@antim/dimsim@0.1.28": { + "integrity": "ad194c3c478a403d31b9320df6fe14d52783a26fa6c8c25a6ea57863926513a4", + "dependencies": [ + "jsr:@dimos/msgs@~0.1.4", + "jsr:@std/http", + "jsr:@std/path@1" + ] + }, + "@antim/dimsim@0.1.29": { + "integrity": "b8d6c2edc8bf82c6b83ac01fa8cc24f16332aadbf8b1e46733bcdf9c7d9e77b6", + "dependencies": [ + "jsr:@dimos/msgs@~0.1.4", + "jsr:@std/http", + "jsr:@std/path@1" + ] + }, + "@dimos/lcm@0.2.0": { + "integrity": "03399f5e4800f28a0c294981e0210d784232fc65a57707de19052ad805bd5fea" + }, + "@dimos/msgs@0.1.4": { + "integrity": "564bc30b4bc41a562c296c257a15055283ca0cbd66d0627991ede5295832d0c4" + }, + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/cli@1.0.28": { + "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a" + }, + "@std/encoding@1.0.10": { + "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" + }, + "@std/fmt@1.0.9": { + "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" + }, + "@std/fs@1.0.23": { + "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37" + }, + "@std/html@1.0.5": { + "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" + }, + "@std/http@1.0.25": { + "integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193", + "dependencies": [ + "jsr:@std/cli", + "jsr:@std/encoding", + "jsr:@std/fmt", + "jsr:@std/fs", + "jsr:@std/html", + "jsr:@std/media-types", + "jsr:@std/net", + "jsr:@std/path@^1.1.4", + "jsr:@std/streams" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/net@1.0.6": { + "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/streams@1.0.17": { + "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" + } + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@dimforge/rapier3d-compat@0.14", + "npm:@sparkjsdev/spark@latest", + "npm:cors@^2.8.5", + "npm:express@^4.21.0", + "npm:openai@^4.77.0", + "npm:three@0.168", + "npm:vite@^5.4.10" + ] + } + } +} diff --git a/misc/DimSim/dimos-cli/README.md b/misc/DimSim/dimos-cli/README.md new file mode 100644 index 0000000000..613e5d6b78 --- /dev/null +++ b/misc/DimSim/dimos-cli/README.md @@ -0,0 +1,58 @@ +# DimSim + +3D simulation environment for the [dimos](https://github.com/dimensionalOS/dimos) robotics stack. + +Browser-based Three.js + Rapier simulator with LCM transport, sensor publishing (RGB, depth, LiDAR, odometry), and an eval harness for automated testing of navigation and perception pipelines. + +## Install + +```sh +deno install -gAf --unstable-net jsr:@antim/dimsim +``` + +## Setup + +Download core assets (~22 MB) and install a scene: + +```sh +dimsim setup +dimsim scene install apt +``` + +## Run + +Start the dev server and open the URL it prints: + +```sh +dimsim dev --scene apt +``` + +Run headless evals in CI: + +```sh +dimsim eval --headless --env apt --workflow reach-vase +``` + +## Programmatic API + +```ts +import { startBridgeServer } from "@antim/dimsim"; + +startBridgeServer({ port: 8090, distDir: "./dist", scene: "apt" }); +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `dimsim setup` | Download core assets | +| `dimsim scene install ` | Install a scene | +| `dimsim scene list` | List available and installed scenes | +| `dimsim scene remove ` | Remove a scene | +| `dimsim dev [--scene ]` | Dev server (open browser manually) | +| `dimsim eval --headless` | Run eval workflows in CI | +| `dimsim agent` | Launch dimos Python agent | + +## License + +MIT diff --git a/misc/DimSim/dimos-cli/agent.py b/misc/DimSim/dimos-cli/agent.py new file mode 100644 index 0000000000..ea68949272 --- /dev/null +++ b/misc/DimSim/dimos-cli/agent.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +DimSim Agent — runs the dimos nav + agent stack connected to DimSim via LCM. + +DimSim acts as the robot (like simplerobot.py but richer): + - Publishes: /odom, /color_image, /lidar, /depth_image + - Subscribes: /cmd_vel + +This script runs the dimos brain that processes those sensors and sends commands. + +Usage (run with dimos venv): + ../dimos/.venv/bin/python dimos-cli/agent.py + ../dimos/.venv/bin/python dimos-cli/agent.py --nav-only # no LLM agent, just exploration +""" + +import argparse + +from dimos.core.blueprints import autoconnect +from dimos.core.transport import JpegLcmTransport, LCMTransport +from dimos.mapping.costmapper import cost_mapper +from dimos.mapping.voxels import voxel_mapper +from dimos.msgs.geometry_msgs import PoseStamped, Twist +from dimos.msgs.sensor_msgs import Image, PointCloud2 +from dimos.navigation.frontier_exploration import wavefront_frontier_explorer +from dimos.navigation.replanning_a_star.module import replanning_a_star_planner +from dimos.protocol.service.lcmservice import autoconf + +# LCM transports — same channels DimSim publishes/subscribes on. +_transports = { + ("color_image", Image): JpegLcmTransport("/color_image", Image), + ("odom", PoseStamped): LCMTransport("/odom", PoseStamped), + ("cmd_vel", Twist): LCMTransport("/cmd_vel", Twist), + ("lidar", PointCloud2): LCMTransport("/lidar", PointCloud2), +} + +# Navigation stack: LiDAR → voxels → costmap → frontier explorer → path planner +nav = ( + autoconnect( + voxel_mapper(voxel_size=0.1), + cost_mapper(algo="simple"), + replanning_a_star_planner(), + wavefront_frontier_explorer(), + ) + .transports(_transports) + .global_config(n_dask_workers=6, robot_model="dimsim") +) + + +def build_agentic(): + """Full agentic: nav + spatial memory + LLM agent + skills.""" + from dimos.agents.agent import llm_agent + from dimos.agents.cli.human import human_input + from dimos.agents.cli.web import web_input + from dimos.agents.skills.navigation import navigation_skill + from dimos.agents.skills.speak_skill import speak_skill + from dimos.perception.spatial_perception import spatial_memory + from dimos.utils.monitoring import utilization + + return autoconnect( + nav, + spatial_memory(), + utilization(), + llm_agent(), + human_input(), + navigation_skill(), + web_input(), + speak_skill(), + ).global_config(n_dask_workers=8) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="DimSim dimos agent") + parser.add_argument( + "--nav-only", + action="store_true", + help="Run nav stack only (no LLM agent)", + ) + args = parser.parse_args() + + autoconf() + + blueprint = nav if args.nav_only else build_agentic() + coordinator = blueprint.build() + + print("DimSim agent running.") + print(" Subscribing: /odom, /color_image, /lidar") + print(" Publishing: /cmd_vel") + print(" Ctrl+C to exit") + + coordinator.loop() diff --git a/misc/DimSim/dimos-cli/bridge/lidar.ts b/misc/DimSim/dimos-cli/bridge/lidar.ts new file mode 100644 index 0000000000..275a0af1f5 --- /dev/null +++ b/misc/DimSim/dimos-cli/bridge/lidar.ts @@ -0,0 +1,284 @@ +/** + * Server-side LiDAR raycasting using Rapier physics world snapshot. + * + * Runs 20K Fibonacci-sphere raycasts at 5 Hz on the Deno bridge server, + * encodes PointCloud2 via @dimos/msgs, and publishes directly to LCM — + * no WebSocket hop needed. + */ + +import { + sensor_msgs, + std_msgs, +} from "@dimos/msgs"; +import type { LCM } from "../vendor/lcm/lcm.ts"; + +// -- Lidar constants (must match engine.js) ----------------------------------- +const NUM_POINTS = 15000; +const MIN_RANGE = 0.1; +const MAX_RANGE = 4; +const V_MIN_RAD = (-30 * Math.PI) / 180; +const V_MAX_RAD = (15 * Math.PI) / 180; +const RATE_MS = 100; // 10 Hz + +const CH_LIDAR = "/lidar#sensor_msgs.PointCloud2"; + +// Agent capsule geometry → lidar mount offset (must match engine.js) +const DEFAULT_HALF_HEIGHT = 0.25; +const DEFAULT_RADIUS = 0.12; +const DEFAULT_LIDAR_MOUNT = 0.35; + +/** Subset of EmbodimentConfig relevant to lidar mount offset. */ +export interface LidarEmbodimentConfig { + radius?: number; + halfHeight?: number; + lidarMountHeight?: number; +} + +// -- _lidarToCamQuat: transforms FLU (x=forward, y=left, z=up) → Three.js camera-local -- +// Matches engine.js _lidarToCamQuat derived from rotation matrix: +// FLU x(forward) → cam -z, FLU y(left) → cam -x, FLU z(up) → cam +y +// Quaternion: (0.5, -0.5, -0.5, -0.5) +const L2C_QX = 0.5, L2C_QY = -0.5, L2C_QZ = -0.5, L2C_QW = -0.5; + +// -- Pre-compute Fibonacci sphere ray directions (pre-rotated to camera-local) - +// Directions are computed in FLU frame then rotated by _lidarToCamQuat so that +// only the agent's yaw quaternion is needed at scan time (cam-local → world). +const fibDirs = (() => { + const golden = (1 + Math.sqrt(5)) / 2; + const zMin = Math.sin(V_MIN_RAD); + const zMax = Math.sin(V_MAX_RAD); + const dirs = new Float32Array(NUM_POINTS * 3); + for (let i = 0; i < NUM_POINTS; i++) { + const z = zMin + (zMax - zMin) * (i + 0.5) / NUM_POINTS; + const r = Math.sqrt(1 - z * z); + const phi = (2 * Math.PI * i) / golden; + const fx = r * Math.cos(phi); // FLU x + const fy = r * Math.sin(phi); // FLU y + const fz = z; // FLU z + + // Rotate FLU → camera-local using _lidarToCamQuat + const tx = 2 * (L2C_QY * fz - L2C_QZ * fy); + const ty = 2 * (L2C_QZ * fx - L2C_QX * fz); + const tz = 2 * (L2C_QX * fy - L2C_QY * fx); + dirs[i * 3 + 0] = fx + L2C_QW * tx + (L2C_QY * tz - L2C_QZ * ty); + dirs[i * 3 + 1] = fy + L2C_QW * ty + (L2C_QZ * tx - L2C_QX * tz); + dirs[i * 3 + 2] = fz + L2C_QW * tz + (L2C_QX * ty - L2C_QY * tx); + } + return dirs; +})(); + +// -- Quaternion rotation helper (q * v) --------------------------------------- +function rotateByQuat( + vx: number, vy: number, vz: number, + qx: number, qy: number, qz: number, qw: number, +): [number, number, number] { + // t = 2 * cross(q.xyz, v) + const tx = 2 * (qy * vz - qz * vy); + const ty = 2 * (qz * vx - qx * vz); + const tz = 2 * (qx * vy - qy * vx); + // result = v + qw * t + cross(q.xyz, t) + return [ + vx + qw * tx + (qy * tz - qz * ty), + vy + qw * ty + (qz * tx - qx * tz), + vz + qw * tz + (qx * ty - qy * tx), + ]; +} + +// -- ServerLidar -------------------------------------------------------------- + +export class ServerLidar { + private lcm: LCM; + private world: any; // RAPIER.World + private RAPIER: any; + private sentSeqs: Set; // echo filter shared with bridge server + private timer: ReturnType | null = null; + private scanCount = 0; + private logN = 0; + private publishing = false; // busy guard — skip scan if previous publish still in flight + private excludeBody: any = null; // rigid body to exclude from raycasting (agent's own colliders) + private lidarYOffset: number; + + // Current robot pose (Three.js Y-up world frame) + private px = 0; + private py = 0; + private pz = 0; + private qx = 0; + private qy = 0; + private qz = 0; + private qw = 1; + private hasPose = false; + + private ray: any; // Reusable Ray object (avoids 20k allocations per scan) + + constructor(lcm: LCM, rapierWorld: any, RAPIER: any, sentSeqs: Set, embodiment?: LidarEmbodimentConfig) { + this.lcm = lcm; + this.world = rapierWorld; + this.RAPIER = RAPIER; + this.sentSeqs = sentSeqs; + this.ray = new RAPIER.Ray({ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }); + + const halfH = embodiment?.halfHeight ?? DEFAULT_HALF_HEIGHT; + const radius = embodiment?.radius ?? DEFAULT_RADIUS; + const mount = embodiment?.lidarMountHeight ?? DEFAULT_LIDAR_MOUNT; + this.lidarYOffset = mount - (halfH + radius); + + // Step once with zero dt to initialize the query pipeline after snapshot restore. + // queryPipeline.update() crashes on restored worlds (WASM type mismatch), + // but world.step() internally updates the pipeline correctly. + this.world.step(); + } + + /** Reconfigure lidar mount offset after embodiment change. */ + reconfigure(embodiment: LidarEmbodimentConfig): void { + const halfH = embodiment.halfHeight ?? DEFAULT_HALF_HEIGHT; + const radius = embodiment.radius ?? DEFAULT_RADIUS; + const mount = embodiment.lidarMountHeight ?? DEFAULT_LIDAR_MOUNT; + this.lidarYOffset = mount - (halfH + radius); + console.log(`[lidar] reconfigured: lidarYOffset=${this.lidarYOffset.toFixed(3)}`); + } + + /** Set rigid body to exclude from raycasting (agent's own capsule). */ + setExcludeBody(body: any): void { + this.excludeBody = body; + } + + /** Update robot pose. Position is capsule center (odom); we apply lidar mount offset internally. */ + updatePose(x: number, y: number, z: number, qx: number, qy: number, qz: number, qw: number): void { + this.px = x; + this.py = y + this.lidarYOffset; // capsule center → lidar mount height + this.pz = z; + this.qx = qx; + this.qy = qy; + this.qz = qz; + this.qw = qw; + this.hasPose = true; + } + + start(): void { + if (this.timer) return; + // quiet + this.timer = setInterval(() => this._scan(), RATE_MS); + } + + stop(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + } + + private _scan(): void { + if (!this.hasPose || this.publishing) return; + this.publishing = true; + this._doScan().catch((e) => { + console.warn("[lidar] publish error (dropped frame):", e?.message || e); + }).finally(() => { this.publishing = false; }); + } + + private async _doScan(): Promise { + this.scanCount++; + const jitterAngle = this.scanCount * 2.399963; // golden angle per scan + const cosJ = Math.cos(jitterAngle); + const sinJ = Math.sin(jitterAngle); + + const RAPIER = this.RAPIER; + const world = this.world; + + // Pre-allocate output buffers + const worldPts = new Float32Array(NUM_POINTS * 3); + const intensity = new Float32Array(NUM_POINTS); + let n = 0; + + const ox = this.px, oy = this.py, oz = this.pz; + const rqx = this.qx, rqy = this.qy, rqz = this.qz, rqw = this.qw; + + for (let i = 0; i < NUM_POINTS; i++) { + // Fibonacci direction (pre-rotated to camera-local) with per-scan golden angle jitter. + // In FLU frame, jitter rotates around Z (up). After lidarToCamQuat, FLU Z → cam Y, + // so jitter must rotate around camera-local Y axis. + const fx = fibDirs[i * 3 + 0], fy = fibDirs[i * 3 + 1], fz = fibDirs[i * 3 + 2]; + const lx = fx * cosJ + fz * sinJ; + const ly = fy; + const lz = -fx * sinJ + fz * cosJ; + + // Rotate local direction by robot quaternion → world direction + const [dx, dy, dz] = rotateByQuat(lx, ly, lz, rqx, rqy, rqz, rqw); + const len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len < 1e-8) continue; + const nx = dx / len, ny = dy / len, nz = dz / len; + + // Reuse ray object — avoids 20k allocations per scan + this.ray.origin.x = ox; this.ray.origin.y = oy; this.ray.origin.z = oz; + this.ray.dir.x = nx; this.ray.dir.y = ny; this.ray.dir.z = nz; + // Use world.castRay (not queryPipeline.castRayAndGetNormal) — + // the pipeline API crashes on restored snapshot worlds. + // Exclude agent's own rigid body so lidar doesn't hit its own colliders. + const hit = world.castRay( + this.ray, MAX_RANGE, false, + undefined, undefined, undefined, + this.excludeBody, + ); + + if (!hit) continue; + const toi = hit.timeOfImpact ?? 0; + if (toi < MIN_RANGE || toi > MAX_RANGE) continue; + + worldPts[n * 3 + 0] = ox + nx * toi; + worldPts[n * 3 + 1] = oy + ny * toi; + worldPts[n * 3 + 2] = oz + nz * toi; + intensity[n] = 1.0 / (1.0 + 0.02 * toi * toi); + n++; + } + + if (n === 0) return; + + this.logN++; + // scan logging removed — too noisy + + // Encode PointCloud2: Y-up → Z-up (ROS) cyclic permutation x→y, y→z, z→x + const pointStep = 16; + const buf = new ArrayBuffer(n * pointStep); + const view = new DataView(buf); + + for (let i = 0; i < n; i++) { + const off = i * pointStep; + const tx = worldPts[i * 3 + 0]; + const ty = worldPts[i * 3 + 1]; + const tz = worldPts[i * 3 + 2]; + view.setFloat32(off, tz, true); // ROS x = Three.js z + view.setFloat32(off + 4, tx, true); // ROS y = Three.js x + view.setFloat32(off + 8, ty, true); // ROS z = Three.js y + view.setFloat32(off + 12, intensity[i], true); + } + + const now = Date.now(); + const header = new std_msgs.Header({ + stamp: new std_msgs.Time({ sec: Math.floor(now / 1000), nsec: (now % 1000) * 1_000_000 }), + frame_id: "world", + }); + + const msg = new sensor_msgs.PointCloud2({ + header, + height: 1, + width: n, + fields_length: 4, + fields: [ + new sensor_msgs.PointField({ name: "x", offset: 0, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "y", offset: 4, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "z", offset: 8, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "intensity", offset: 12, datatype: 7, count: 1 }), + ], + is_bigendian: false, + point_step: pointStep, + row_step: n * pointStep, + data_length: n * pointStep, + data: new Uint8Array(buf), + is_dense: true, + }); + + // Mark seq for echo filtering (prevent server re-forwarding to browser WS) + this.sentSeqs.add(this.lcm.getNextSeq()); + // Publish directly to LCM — no WS hop (await so buffer pressure is felt) + await this.lcm.publish(CH_LIDAR, msg); + } +} diff --git a/misc/DimSim/dimos-cli/bridge/physics.ts b/misc/DimSim/dimos-cli/bridge/physics.ts new file mode 100644 index 0000000000..e902de3fc9 --- /dev/null +++ b/misc/DimSim/dimos-cli/bridge/physics.ts @@ -0,0 +1,385 @@ +/** + * Server-side agent physics (Deno/Rapier). + * + * Runs the agent's kinematic character controller at a fixed timestep on the + * server, eliminating the browser from the control loop: + * + * Python cmd_vel → LCM → Deno (physics step) → LCM odom → Python + * ↓ + * WS position → Browser (render only) + * + * The browser no longer integrates cmd_vel or steps physics — it just receives + * position updates and moves the visual avatar. + */ + +import { geometry_msgs, std_msgs } from "@dimos/msgs"; + +import type { LCM } from "../vendor/lcm/lcm.ts"; + +// -- Agent dimensions (must match AiAvatar.js / engine.js) -------------------- +const DEFAULT_AGENT_RADIUS = 0.12; +const DEFAULT_AGENT_HALF_HEIGHT = 0.25; +const CONTROLLER_OFFSET = 0.05; + +// -- Physics constants -------------------------------------------------------- +const PHYSICS_HZ = 50; +const PHYSICS_DT = 1.0 / PHYSICS_HZ; +const DEFAULT_GRAVITY_Y = -9.81; +const DEFAULT_SPEED_SCALE = 3.0; // Multiplier for cmd_vel (linear + angular) +const DEFAULT_TURN_SCALE = 3.0; +const DEFAULT_MAX_ALTITUDE = 50; + +/** Embodiment configuration passed from SceneClient / control channel. */ +export interface EmbodimentConfig { + radius?: number; + halfHeight?: number; + lidarMountHeight?: number; + embodimentType?: string; // "ground" | "drone" + maxSpeed?: number; + turnRate?: number; + gravity?: number; + maxStepHeight?: number; + groundSnapDist?: number; + maxSlopeAngle?: number; + friction?: number; + maxAltitude?: number; +} + +const CH_ODOM = "/odom#geometry_msgs.PoseStamped"; +const CH_CMD_VEL = "/cmd_vel#geometry_msgs.Twist"; +const CMD_VEL_TIMEOUT_MS = 500; + +// -- ServerPhysics ------------------------------------------------------------ + +export class ServerPhysics { + private lcm: LCM; + private world: any; // RAPIER.World + private RAPIER: any; + private sentSeqs: Set; + + private body: any; + private collider: any; + private spineCollider: any; + private controller: any; + private timer: ReturnType | null = null; + + // Embodiment params + private embodimentType: string; + private speedScale: number; + private turnScale: number; + private gravity: number; + private maxAltitude: number; + private agentRadius: number; + private agentHalfHeight: number; + private friction: number; + private maxStepHeight: number; + private groundSnapDist: number; + private maxSlopeAngle: number; + + // Agent state + private yaw = 0; + private seq = 0; + + // cmd_vel (ROS frame: x=fwd, z=yaw) + private linX = 0; // forward + private linY = 0; // lateral + private linZ = 0; // vertical + private angZ = 0; // yaw rotation + private cmdVelStamp = 0; + + // Callback to send position to browser + private onPoseUpdate: ((x: number, y: number, z: number, yaw: number) => void) | null = null; + + constructor( + lcm: LCM, + rapierWorld: any, + RAPIER: any, + sentSeqs: Set, + embodiment?: EmbodimentConfig, + ) { + this.lcm = lcm; + this.world = rapierWorld; + this.RAPIER = RAPIER; + this.sentSeqs = sentSeqs; + + // Apply embodiment config with defaults + this.embodimentType = embodiment?.embodimentType ?? "ground"; + this.speedScale = embodiment?.maxSpeed ?? DEFAULT_SPEED_SCALE; + this.turnScale = embodiment?.turnRate ?? DEFAULT_TURN_SCALE; + this.gravity = embodiment?.gravity ?? DEFAULT_GRAVITY_Y; + this.maxAltitude = embodiment?.maxAltitude ?? DEFAULT_MAX_ALTITUDE; + this.agentRadius = embodiment?.radius ?? DEFAULT_AGENT_RADIUS; + this.agentHalfHeight = embodiment?.halfHeight ?? DEFAULT_AGENT_HALF_HEIGHT; + this.friction = embodiment?.friction ?? 0.8; + this.maxStepHeight = embodiment?.maxStepHeight ?? 0.25; + this.groundSnapDist = embodiment?.groundSnapDist ?? 0.5; + this.maxSlopeAngle = embodiment?.maxSlopeAngle ?? 45; + + this._createBodyAndColliders(); + + // Count colliders to verify world integrity + let colliderCount = 0; + this.world.colliders.forEach(() => { colliderCount++; }); + // Quiet init — only log on error or reconfigure + } + + private _createBodyAndColliders(): void { + const RAPIER = this.RAPIER; + + // Create agent body (kinematic position-based, like AiAvatar) + this.body = this.world.createRigidBody( + RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(0, 3, 0), + ); + + // Main capsule collider + this.collider = this.world.createCollider( + RAPIER.ColliderDesc.capsule(this.agentHalfHeight, this.agentRadius) + .setFriction(this.friction), + this.body, + ); + + // Spine collider (horizontal, behind body center — matches AiAvatar) + const spineHalfLen = Math.max(this.agentRadius * 1.2, 0.13); + const spineRadius = Math.max(this.agentRadius * 0.62, 0.07); + const spineOffsetBack = Math.max( + this.agentRadius * 2.2, + spineHalfLen + spineRadius + 0.02, + ); + const spineOffsetY = Math.max(this.agentHalfHeight * 0.35, 0.08); + this.spineCollider = this.world.createCollider( + RAPIER.ColliderDesc.capsule(spineHalfLen, spineRadius) + .setFriction(this.friction) + .setTranslation(0, spineOffsetY, -spineOffsetBack) + .setRotation({ + x: Math.SQRT1_2, + y: 0, + z: 0, + w: Math.SQRT1_2, + }), + this.body, + ); + + // Character controller + this.controller = this.world.createCharacterController(CONTROLLER_OFFSET); + this.controller.enableAutostep(this.maxStepHeight, 0.15, true); + this.controller.enableSnapToGround(this.groundSnapDist); + this.controller.setSlideEnabled(true); + this.controller.setMaxSlopeClimbAngle((this.maxSlopeAngle * Math.PI) / 180); + this.controller.setMinSlopeSlideAngle((75 * Math.PI) / 180); + } + + /** Reconfigure physics with new embodiment params (e.g. after set_embodiment). */ + reconfigure(embodiment: EmbodimentConfig): void { + // Save current position and yaw + const pos = this.body.translation(); + const savedYaw = this.yaw; + + // Update params + this.embodimentType = embodiment.embodimentType ?? this.embodimentType; + this.speedScale = embodiment.maxSpeed ?? this.speedScale; + this.turnScale = embodiment.turnRate ?? this.turnScale; + this.gravity = embodiment.gravity ?? this.gravity; + this.maxAltitude = embodiment.maxAltitude ?? this.maxAltitude; + this.agentRadius = embodiment.radius ?? this.agentRadius; + this.agentHalfHeight = embodiment.halfHeight ?? this.agentHalfHeight; + this.friction = embodiment.friction ?? this.friction; + this.maxStepHeight = embodiment.maxStepHeight ?? this.maxStepHeight; + this.groundSnapDist = embodiment.groundSnapDist ?? this.groundSnapDist; + this.maxSlopeAngle = embodiment.maxSlopeAngle ?? this.maxSlopeAngle; + + // Remove old colliders and body + if (this.spineCollider) this.world.removeCollider(this.spineCollider, false); + if (this.collider) this.world.removeCollider(this.collider, false); + if (this.body) this.world.removeRigidBody(this.body); + + // Recreate with new params + this._createBodyAndColliders(); + + // Restore position and yaw + this.body.setNextKinematicTranslation({ x: pos.x, y: pos.y, z: pos.z }); + this.yaw = savedYaw; + this.world.step(); + + console.log(`[physics] reconfigured: type=${this.embodimentType} radius=${this.agentRadius} halfHeight=${this.agentHalfHeight} speed=${this.speedScale} gravity=${this.gravity}`); + } + + /** Set spawn position (Three.js Y-up). */ + setPosition(x: number, y: number, z: number): void { + this.body.setNextKinematicTranslation({ x, y, z }); + this.world.step(); // apply immediately + // quiet + } + + /** Set callback for browser position sync. */ + setOnPoseUpdate( + cb: (x: number, y: number, z: number, yaw: number) => void, + ): void { + this.onPoseUpdate = cb; + } + + /** Handle incoming cmd_vel (ROS frame). */ + handleCmdVel(twist: any): void { + this.linX = twist.linear.x; // forward (ROS +x) + this.linY = twist.linear.y; // lateral + this.linZ = twist.linear.z; // vertical + this.angZ = twist.angular.z; // yaw (ROS +z = rotate left) + this.cmdVelStamp = Date.now(); + } + + /** Subscribe to cmd_vel on LCM. */ + subscribeCmdVel(): void { + this.lcm.subscribe(CH_CMD_VEL, geometry_msgs.Twist, (msg: any) => { + this.handleCmdVel(msg.data); + }); + // quiet + } + + /** Start fixed-rate physics stepping + odom publish. */ + start(): void { + if (this.timer) return; + this.subscribeCmdVel(); + this.timer = setInterval(() => this._step(), 1000 / PHYSICS_HZ); + // quiet + } + + stop(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + } + + /** Get current position in Three.js Y-up frame. */ + getPosition(): { x: number; y: number; z: number } { + return this.body.translation(); + } + + /** Get the agent's rigid body (for lidar exclusion). */ + getBody(): any { + return this.body; + } + + getYaw(): number { + return this.yaw; + } + + private _step(): void { + // Safety timeout — zero velocity if no cmd_vel received recently + const hasVel = Date.now() - this.cmdVelStamp < CMD_VEL_TIMEOUT_MS; + const linX = hasVel ? this.linX * this.speedScale : 0; + const linY = hasVel ? this.linY * this.speedScale : 0; + const linZ = hasVel ? this.linZ * this.speedScale : 0; + const angZ = hasVel ? this.angZ * this.turnScale : 0; + + // Integrate yaw (ROS angZ → Three.js Y rotation) + // ROS +z yaw = CCW from above = Three.js +Y rotation + this.yaw += angZ * PHYSICS_DT; + + const pos = this.body.translation(); + const cosY = Math.cos(this.yaw); + const sinY = Math.sin(this.yaw); + + let newPos: { x: number; y: number; z: number }; + + if (this.embodimentType === "drone") { + // Drone: 6DoF movement, no gravity, altitude clamping + const fwd = linX; + const lat = linY; + const vert = linZ; // ROS z = vertical for drone + const desired = { + x: (fwd * sinY + lat * cosY) * PHYSICS_DT, + y: vert * PHYSICS_DT, + z: (fwd * cosY - lat * sinY) * PHYSICS_DT, + }; + + this.controller.computeColliderMovement( + this.collider, + desired, + this.RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + ); + const m = this.controller.computedMovement(); + newPos = { + x: pos.x + m.x, + y: Math.min(pos.y + m.y, this.maxAltitude), + z: pos.z + m.z, + }; + } else { + // Ground robot: gravity, collision-aware + const fwd = linX; + const desired = { + x: (fwd * sinY) * PHYSICS_DT, + y: this.gravity * PHYSICS_DT * PHYSICS_DT * 0.5, // gravity + z: (fwd * cosY) * PHYSICS_DT, + }; + + this.controller.computeColliderMovement( + this.collider, + desired, + this.RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + ); + const m = this.controller.computedMovement(); + newPos = { + x: pos.x + m.x, + y: pos.y + m.y, + z: pos.z + m.z, + }; + } + + this.body.setNextKinematicTranslation(newPos); + + // Step world to apply kinematic translation (needed for next computeColliderMovement) + this.world.step(); + + // Publish odom to LCM (Three.js Y-up → ROS Z-up) + this._publishOdom(newPos); + + // Debug: log first few steps + // step logging removed — too noisy for dimos subprocess output + + // Notify browser for visual sync + if (this.onPoseUpdate) { + this.onPoseUpdate(newPos.x, newPos.y, newPos.z, this.yaw); + } + } + + private _publishOdom(pos: { x: number; y: number; z: number }): void { + // Three.js Y-up → ROS Z-up: (x,y,z) → (z,x,y) + const rosX = pos.z; + const rosY = pos.x; + const rosZ = pos.y; + + // Yaw quaternion (Three.js Y-axis → ROS Z-axis) + const qw = Math.cos(this.yaw / 2); + const qRosZ = Math.sin(this.yaw / 2); // rotation about ROS Z + + const now = Date.now(); + + const header = new std_msgs.Header({ + seq: this.seq++, + stamp: new std_msgs.Time({ sec: Math.floor(now / 1000), nsec: (now % 1000) * 1_000_000 }), + frame_id: "world", + }); + + const pose = new geometry_msgs.Pose(); + pose.position = new geometry_msgs.Point(); + pose.position.x = rosX; + pose.position.y = rosY; + pose.position.z = rosZ; + pose.orientation = new geometry_msgs.Quaternion(); + pose.orientation.x = 0; + pose.orientation.y = 0; + pose.orientation.z = qRosZ; + pose.orientation.w = qw; + + const odom = new geometry_msgs.PoseStamped(); + odom.header = header; + odom.pose = pose; + + try { + this.sentSeqs.add(this.lcm.getNextSeq()); + this.lcm.publishRaw(CH_ODOM, odom.encode()).catch(() => {}); + } catch (e: unknown) { + if (this.seq <= 3) console.warn("[physics] odom publish error:", e); + } + } +} diff --git a/misc/DimSim/dimos-cli/bridge/server.ts b/misc/DimSim/dimos-cli/bridge/server.ts new file mode 100644 index 0000000000..6824a2ee55 --- /dev/null +++ b/misc/DimSim/dimos-cli/bridge/server.ts @@ -0,0 +1,349 @@ +#!/usr/bin/env -S deno run --allow-net --allow-read --unstable-net + +/** + * DimSim Bridge Server + * + * - One control WebSocket plus multiple sensor WebSockets. + * Separate TCP streams so large sensor data never blocks real-time odom + * or other sensor streams. + * - LCM multicast relay (WS ↔ LCM) + * - Per-channel isolation for multi-page parallel evals + * - Static file server for the pre-built DimSim frontend (dist/) + * - Uses vendored LCM transport with joinMulticastV4 fix + */ + +import { LCM } from "../vendor/lcm/lcm.ts"; +import { decodePacket } from "../vendor/lcm/transport.ts"; +import { MAGIC_SHORT, SHORT_HEADER_SIZE } from "../vendor/lcm/types.ts"; +import { serveDir } from "@std/http/file-server"; +import { ServerLidar } from "./lidar.ts"; +import { ServerPhysics } from "./physics.ts"; +import { geometry_msgs } from "@dimos/msgs"; + +// Magic prefix for Rapier world snapshot (ASCII "DSSN") +const SNAPSHOT_MAGIC = 0x4453534E; +const DEFAULT_LCM_PORT = 7667; +const DEFAULT_LCM_HOST = "239.255.76.67"; + +export interface BridgeServerOptions { + port: number; + distDir: string; + scene?: string; + evalOnly?: boolean; + headless?: boolean; + channels?: string[]; + lcmBasePort?: number; + sensorRates?: Record; + sensorEnable?: Record; + cameraFov?: number; +} + +/** Per-channel state: each channel gets its own LCM, physics, lidar, and WS client sets. */ +interface ChannelState { + name: string; + controlClients: Set; + activeControlClient: WebSocket | null; + sensorClients: Set; + lcm: LCM | null; + sentSeqs: Set; + serverLidar: ServerLidar | null; + serverPhysics: ServerPhysics | null; + embodiment: Record | null; +} + +export async function startBridgeServer(options: BridgeServerOptions) { + const { + port, distDir, scene, + evalOnly = false, headless = false, + channels, lcmBasePort = DEFAULT_LCM_PORT, + sensorRates, sensorEnable, cameraFov, + } = options; + + // Build channel list: if channels provided, use them; otherwise single default + const channelNames = channels && channels.length > 0 + ? channels + : [""]; // empty string = default (backward compat, no channel routing) + + const channelMap = new Map(); + + for (let i = 0; i < channelNames.length; i++) { + const name = channelNames[i]; + const lcmPort = lcmBasePort + i; + const lcmUrl = `udpm://${DEFAULT_LCM_HOST}:${lcmPort}?ttl=0`; + const state: ChannelState = { + name, + controlClients: new Set(), + activeControlClient: null, + sensorClients: new Set(), + lcm: null, + sentSeqs: new Set(), + serverLidar: null, + serverPhysics: null, + embodiment: null, + }; + + if (!evalOnly) { + state.lcm = new LCM(lcmUrl); + await state.lcm.start(); + console.log(`[bridge] channel "${name || "default"}" LCM on ${lcmUrl}`); + + // LCM → WS: forward external packets to this channel's active control client + state.lcm.subscribePacket((packet: Uint8Array) => { + if (packet.length < 8) return; + const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength); + const magic = view.getUint32(0, false); + if (magic !== MAGIC_SHORT) return; + + const seq = view.getUint32(4, false); + if (state.sentSeqs.has(seq)) { + state.sentSeqs.delete(seq); + return; + } + if (state.sentSeqs.size > 1000) state.sentSeqs.clear(); + + const copy = packet.slice(); + const client = state.activeControlClient; + if (client && client.readyState === WebSocket.OPEN) client.send(copy); + }); + } + + channelMap.set(name, state); + } + + /** Resolve channel from WS query param. Falls back to default ("") if not found. */ + function resolveChannel(channelParam: string | null): ChannelState { + if (channelParam && channelMap.has(channelParam)) { + return channelMap.get(channelParam)!; + } + // If no channel param or unknown, use default (first channel) + return channelMap.values().next().value!; + } + + // -- Server-side init from Rapier snapshot ---------------------------------- + async function initServerSystems( + chState: ChannelState, + snapshot: Uint8Array, + spawnPos?: { x: number; y: number; z: number }, + ): Promise { + if (chState.serverLidar) { chState.serverLidar.stop(); chState.serverLidar = null; } + if (chState.serverPhysics) { chState.serverPhysics.stop(); chState.serverPhysics = null; } + if (!chState.lcm) return; + + try { + const RAPIER = await import("@dimforge/rapier3d-compat"); + await RAPIER.init(); + const world = RAPIER.World.restoreSnapshot(snapshot); + if (!world) { console.error(`[bridge:${chState.name || "default"}] failed to restore Rapier snapshot`); return; } + + const bodiesToRemove: any[] = []; + world.bodies.forEach((body: any) => { + if (!body.isFixed()) bodiesToRemove.push(body.handle); + }); + for (const handle of bodiesToRemove) { + world.removeRigidBody(world.getRigidBody(handle)); + } + console.log(`[bridge:${chState.name || "default"}] Rapier snapshot restored (removed ${bodiesToRemove.length} non-fixed bodies)`); + + chState.serverPhysics = new ServerPhysics(chState.lcm, world, RAPIER, chState.sentSeqs, chState.embodiment ?? undefined); + if (spawnPos) { + chState.serverPhysics.setPosition(spawnPos.x, spawnPos.y, spawnPos.z); + } + + chState.serverLidar = new ServerLidar(chState.lcm, world, RAPIER, chState.sentSeqs, chState.embodiment ?? undefined); + chState.serverLidar.setExcludeBody(chState.serverPhysics.getBody()); + + chState.serverPhysics.setOnPoseUpdate((x, y, z, yaw) => { + const qw = Math.cos(yaw / 2); + const qy = Math.sin(yaw / 2); + chState.serverLidar!.updatePose(x, y, z, 0, qy, 0, qw); + + const msg = JSON.stringify({ type: "pose", x, y, z, yaw }); + const client = chState.activeControlClient; + if (client && client.readyState === WebSocket.OPEN) { + try { client.send(msg); } catch { /* ignore */ } + } + }); + + chState.serverPhysics.start(); + chState.serverLidar.start(); + } catch (e) { + console.error(`[bridge:${chState.name || "default"}] server systems init error:`, e); + } + } + + // ── HTTP + WebSocket server ───────────────────────────────────────────── + Deno.serve({ port }, async (req: Request) => { + const url = new URL(req.url); + + if (req.headers.get("upgrade") === "websocket") { + const { socket, response } = Deno.upgradeWebSocket(req); + socket.binaryType = "arraybuffer"; + const ch = url.searchParams.get("ch") || "control"; + const channelParam = url.searchParams.get("channel"); + const chState = resolveChannel(channelParam); + const isSensor = ch !== "control"; + const logPrefix = `[bridge:${chState.name || "default"}]`; + + if (isSensor) { + // ── SENSOR WebSocket ────────────────────────────────────────── + socket.onopen = () => { chState.sensorClients.add(socket); }; + socket.onclose = () => { chState.sensorClients.delete(socket); }; + socket.onerror = () => chState.sensorClients.delete(socket); + + let _sensorLogN = 0; + socket.onmessage = (event: MessageEvent) => { + if (!(event.data instanceof ArrayBuffer) || !chState.lcm) return; + const packet = new Uint8Array(event.data); + + // Check for Rapier snapshot + if (packet.length > 4) { + const dv = new DataView(packet.buffer, packet.byteOffset); + const magic = dv.getUint32(0, false); + + if (magic === 0x44535332) { // "DSS2" + const sx = dv.getFloat32(4, true); + const sy = dv.getFloat32(8, true); + const sz = dv.getFloat32(12, true); + const snapshot = packet.slice(16); + const spawnPos = { x: sx, y: sy, z: sz }; + console.log(`${logPrefix} Rapier snapshot received (${(snapshot.byteLength / 1024).toFixed(0)}KB) spawn=(${sx.toFixed(1)},${sy.toFixed(1)},${sz.toFixed(1)})`); + initServerSystems(chState, snapshot, spawnPos); + return; + } + + if (magic === SNAPSHOT_MAGIC) { // "DSSN" + const snapshot = packet.slice(4); + console.log(`${logPrefix} Rapier snapshot received (${(snapshot.byteLength / 1024).toFixed(0)}KB) [legacy, no spawn]`); + initServerSystems(chState, snapshot); + return; + } + } + + try { + const decoded = decodePacket(packet); + if (decoded && decoded.type === "small") { + _sensorLogN++; + if (_sensorLogN === 1 || _sensorLogN % 1000 === 0) { + const chName = decoded.channel.split("#")[0].replace("/", ""); + // quiet — sensor relay logging removed + } + chState.sentSeqs.add(chState.lcm.getNextSeq()); + chState.lcm.publishRaw(decoded.channel, decoded.data).catch(() => {}); + } + } catch { /* ignore */ } + }; + } else { + // ── CONTROL WebSocket ───────────────────────────────────────── + socket.onopen = () => { + if (!chState.activeControlClient || chState.activeControlClient.readyState !== WebSocket.OPEN) { + chState.activeControlClient = socket; + } + chState.controlClients.add(socket); + // quiet + }; + socket.onerror = () => chState.controlClients.delete(socket); + + let _odomLogN = 0; + + socket.onclose = () => { + chState.controlClients.delete(socket); + if (chState.activeControlClient === socket) chState.activeControlClient = null; + // quiet + }; + + socket.onmessage = (event: MessageEvent) => { + // Text messages: handle special types, relay the rest + if (typeof event.data === "string") { + try { + const msg = JSON.parse(event.data); + + // -- Embodiment config: store & reconfigure running systems -- + if (msg.type === "embodimentConfig") { + chState.embodiment = msg.config ?? msg; + console.log(`${logPrefix} embodiment config stored:`, JSON.stringify(chState.embodiment)); + if (chState.serverPhysics) chState.serverPhysics.reconfigure(chState.embodiment as any); + if (chState.serverLidar) chState.serverLidar.reconfigure(chState.embodiment as any); + // fall through to relay to browser + } + + // -- Teleport: reposition physics agent, don't relay -- + if (msg.type === "teleport") { + if (chState.serverPhysics && msg.x != null && msg.y != null && msg.z != null) { + chState.serverPhysics.setPosition(msg.x, msg.y, msg.z); + console.log(`${logPrefix} teleport to (${msg.x},${msg.y},${msg.z})`); + } + return; // don't relay teleport commands + } + + // -- Physics collider add/remove: forward to Rapier world -- + if (msg.type === "physicsColliderAdd" || msg.type === "physicsColliderRemove") { + // These are handled by the browser's physics; just relay + } + } catch { /* not JSON, relay as-is */ } + + for (const client of chState.controlClients) { + if (client !== socket && client.readyState === WebSocket.OPEN) { + try { client.send(event.data); } catch { /* ignore */ } + } + } + return; + } + if (!(event.data instanceof ArrayBuffer) || !chState.lcm) return; + if (chState.activeControlClient !== socket) return; + const packet = new Uint8Array(event.data); + try { + const decoded = decodePacket(packet); + if (decoded && decoded.type === "small") { + _odomLogN++; + + if (chState.serverPhysics && decoded.channel === "/odom#geometry_msgs.PoseStamped") { + return; + } + + chState.sentSeqs.add(chState.lcm.getNextSeq()); + chState.lcm.publishRaw(decoded.channel, decoded.data).catch(() => {}); + } + } catch { /* ignore */ } + }; + } + + return response; + } + + if (url.pathname === "/" || url.pathname === "/index.html") { + try { + let html = await Deno.readTextFile(`${distDir}/index.html`); + const ratesJs = sensorRates ? `window.__dimosSensorRates=${JSON.stringify(sensorRates)};` : ""; + const enableJs = sensorEnable ? `window.__dimosSensorEnable=${JSON.stringify(sensorEnable)};` : ""; + const fovJs = cameraFov ? `window.__dimosCameraFov=${cameraFov};` : ""; + const inject = ``; + html = html.replace("", `${inject}\n`); + return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } }); + } catch { + return new Response("index.html not found", { status: 404 }); + } + } + + return serveDir(req, { fsRoot: distDir, quiet: true }); + }); + + const channelInfo = channelNames.length > 1 + ? ` (${channelNames.length} channels: ${channelNames.join(", ")})` + : ""; + console.log(`[bridge] :${port}${evalOnly ? " (eval-only)" : " (LCM bridge)"}${channelInfo}`); + + // Run all LCM instances + const lcmInstances = [...channelMap.values()].map(s => s.lcm).filter(Boolean) as LCM[]; + if (lcmInstances.length > 0) { + await Promise.all(lcmInstances.map(l => l.run())); + } else { + await new Promise(() => {}); + } +} + +if (import.meta.main) { + const distDir = new URL("../../dist", import.meta.url).pathname; + const scene = Deno.args.find((_a: string, i: number, arr: string[]) => arr[i - 1] === "--scene") || "apt"; + const port = parseInt(Deno.args.find((_a: string, i: number, arr: string[]) => arr[i - 1] === "--port") || "8090"); + await startBridgeServer({ port, distDir, scene }); +} diff --git a/misc/DimSim/dimos-cli/cli.ts b/misc/DimSim/dimos-cli/cli.ts new file mode 100644 index 0000000000..af712b218e --- /dev/null +++ b/misc/DimSim/dimos-cli/cli.ts @@ -0,0 +1,873 @@ +#!/usr/bin/env -S deno run --allow-all --unstable-net + +/** + * DimSim CLI — 3D simulation, eval runner, dev server, and scene manager. + * + * Usage: + * dimsim setup Download core assets + * dimsim scene install Download a scene + * dimsim scene list List scenes + * dimsim scene remove Remove a scene + * dimsim dev [--scene ] [--port ] Dev server + browser + * dimsim eval create Interactive eval wizard + * dimsim eval [--headless] [--parallel N] [--render gpu] Headless CI evals + * dimsim agent [--nav-only] dimos Python agent + */ + +import { resolve, dirname, fromFileUrl } from "@std/path"; +import { startBridgeServer } from "./bridge/server.ts"; +import { launchHeadless, launchMultiPage, type RenderMode } from "./headless/launcher.ts"; +import { runEvals, runEvalsMultiPage, collectWorkflows, toJunitXml, type EvalResult } from "./eval/runner.ts"; +import { getDimsimHome, getDistDir, setup, sceneInstall, sceneList, sceneRemove } from "./setup.ts"; +import { loadSceneIndex, findObject, suggestObjects } from "./eval/scene-index.ts"; +import { buildEval } from "./eval/builder.ts"; + +// Detect compiled binary: Deno.execPath() won't contain "deno" when compiled. +// When compiled or installed from JSR, local source paths don't exist. +const IS_COMPILED = !Deno.execPath().toLowerCase().includes("deno"); +const IS_REMOTE = IS_COMPILED || !import.meta.url.startsWith("file:"); + +const CLI_DIR = IS_REMOTE ? null : dirname(fromFileUrl(import.meta.url)); +const PROJECT_DIR = CLI_DIR ? resolve(CLI_DIR, "..") : null; +const LOCAL_DIST_DIR = PROJECT_DIR ? resolve(PROJECT_DIR, "dist") : null; +const EVALS_DIR = PROJECT_DIR ? resolve(PROJECT_DIR, "evals") : `${getDimsimHome()}/evals`; +const DIMOS_VENV = PROJECT_DIR ? resolve(PROJECT_DIR, "../dimos/.venv/bin/python") : null; +const AGENT_PY = CLI_DIR ? resolve(CLI_DIR, "agent.py") : null; + +/** + * Build dist/ from the repo's own sources using Deno's npm compat. + * Everything needed is already in-tree: src/ (engine), public/ (scenes, + * agent-model, logo). Vite bundles src/ and copies public/ verbatim, so + * no assets need to be downloaded from GitHub releases, and npm/Node are + * not required — Deno runs Vite directly. + * + * --no-lock keeps the repo's deno.lock (which tracks only JSR deps for + * the CLI) from being polluted with the frontend's npm dep graph. + */ +async function tryBuildFromSource( + projectDir: string, + distDir: string, +): Promise { + let viteSpec = "npm:vite@^5"; + try { + const pkg = JSON.parse(await Deno.readTextFile(`${projectDir}/package.json`)); + const v = pkg.devDependencies?.vite ?? pkg.dependencies?.vite; + if (!v) return false; + viteSpec = `npm:vite@${v}`; + } catch { + return false; + } + + // node_modules/ — install from package.json if missing. Vite resolves + // bare imports (three, rapier, etc.) via node_modules at build time. + try { + await Deno.stat(`${projectDir}/node_modules`); + } catch { + console.log(`[dimsim] node_modules/ not found — running 'deno install' (one-time)...`); + const install = new Deno.Command(Deno.execPath(), { + args: ["install", "--no-lock"], cwd: projectDir, + stdout: "inherit", stderr: "inherit", + }).spawn(); + const s = await install.status; + if (!s.success) { + console.error(`[dimsim] deno install failed (exit ${s.code}).`); + return false; + } + } + + console.log(`[dimsim] Building frontend with Vite...`); + const build = new Deno.Command(Deno.execPath(), { + args: ["run", "-A", "--no-lock", viteSpec, "build"], + cwd: projectDir, + stdout: "inherit", stderr: "inherit", + }).spawn(); + const bs = await build.status; + if (!bs.success) { + console.error(`[dimsim] vite build failed (exit ${bs.code}).`); + return false; + } + + try { + await Deno.stat(`${distDir}/index.html`); + return true; + } catch { + console.error(`[dimsim] Build completed but ${distDir}/index.html is missing.`); + return false; + } +} + +/** Resolve distDir: use local dist/ if it exists (dev), else ~/.dimsim/dist/ (installed). */ +async function resolveDistDir(): Promise { + // Check local dist/ (only in dev mode, running from source) + if (LOCAL_DIST_DIR && PROJECT_DIR) { + try { + await Deno.stat(`${LOCAL_DIST_DIR}/index.html`); + return LOCAL_DIST_DIR; + } catch { /* not found — try to build from repo sources */ } + + if (await tryBuildFromSource(PROJECT_DIR, LOCAL_DIST_DIR)) { + return LOCAL_DIST_DIR; + } + } + + const installed = getDistDir(); + try { + await Deno.stat(`${installed}/index.html`); + return installed; + } catch { /* not found */ } + + console.error(`[dimsim] No dist/ found.`); + console.error(`[dimsim] Run 'dimsim setup' to download core assets.`); + Deno.exit(1); + throw new Error("unreachable"); // for TS flow analysis — Deno.exit is never +} + +function printUsage() { + console.log(` +DimSim CLI — 3D simulation + eval harness for dimos + +Commands: + dimsim setup Download core assets (~40MB) + dimsim scene install Download a scene + dimsim scene list List available + installed scenes + dimsim scene remove Remove a local scene + dimsim dev [options] Dev server (open browser, optional eval) + dimsim eval list List installed eval workflows + dimsim eval create Interactive eval builder wizard + dimsim eval [options] Run eval workflows (headless CI) + dimsim list objects [options] List scene objects (eval targets) + dimsim build eval [options] Generate eval from validated target + dimsim agent [options] Launch dimos Python agent + +Setup: + --local Use local archive instead of downloading + +Dev: + --scene Scene to load (default: apt) + --port Server port (default: 8090) + --headless Launch headless browser (no GUI) + --render gpu|cpu Render mode for headless (default: gpu) + --channels Number of parallel browser pages (multi-instance) + --eval Run eval after browser connects + --env Environment filter + --image-rate Image publish interval in ms (default: 500 = 2 Hz) + --lidar-rate LiDAR publish interval in ms (default: 200 = 5 Hz) + --odom-rate Odom publish interval in ms (default: 20 = 50 Hz) + --no-depth Disable depth image publishing + --camera-fov Camera FOV in degrees (default: 80) + +Eval: + --connect Connect to existing bridge (use with dimos) + --headless Headless Chromium (required for CI) + --parallel N parallel browser pages (default: 1) + --render gpu|cpu gpu = Metal/ANGLE, cpu = SwiftShader (default: cpu) + --env Filter to environment + --workflow Filter to workflow + --output json|junit Output format (default: json) + --port Bridge port (default: 8090) + --timeout Engine init timeout (default: auto) + +List Objects: + --scene Scene to inspect (required) + --search Filter objects by name + +Build Eval: + --scene Scene name (required) + --target Target object name (required, validated) + --threshold Distance threshold (default: 2.0) + --timeout Timeout in seconds (default: 60) + --task Agent prompt (default: auto from target) + --name Eval name (default: slugified target) + --env Manifest environment (default: scene name) + +Agent: + --nav-only Nav stack only (no LLM agent) + --venv Python venv path (default: ../dimos/.venv/bin/python) + +Environment: + DIMSIM_HOME Override data dir (default: ~/.dimsim) +`); +} + +function parseArgs(args: string[]) { + const opts: Record = {}; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg.startsWith("--")) { + const key = arg.slice(2); + const next = args[i + 1]; + if (next && !next.startsWith("--")) { + opts[key] = next; + i++; + } else { + opts[key] = true; + } + } + } + return opts; +} + +async function main() { + const subcommand = Deno.args[0]; + const opts = parseArgs(Deno.args.slice(1)); + + if (!subcommand || subcommand === "help" || subcommand === "--help") { + printUsage(); + Deno.exit(0); + } + + if (subcommand === "--version" || subcommand === "version") { + if (IS_COMPILED) { + // Version is read from the embedded deno.json at compile time + try { + const text = await Deno.readTextFile(new URL("./deno.json", import.meta.url)); + console.log(JSON.parse(text).version); + } catch { + console.log("0.1.31"); // fallback — updated at release time + } + } else { + const metaUrl = new URL("./deno.json", import.meta.url); + try { + const resp = await fetch(metaUrl); + const meta = await resp.json(); + console.log(meta.version); + } catch { + console.log("unknown"); + } + } + Deno.exit(0); + } + + const port = parseInt(opts.port as string) || 8090; + + // ── Setup ─────────────────────────────────────────────────────────── + if (subcommand === "setup") { + const local = opts.local; + if (local === true) { + console.error("[dimsim] --local requires a path: dimsim setup --local ./dimsim-core-v0.1.0.tar.gz"); + Deno.exit(1); + } + await setup(local as string | undefined); + Deno.exit(0); + } + + // ── Scene management ──────────────────────────────────────────────── + if (subcommand === "scene") { + const action = Deno.args[1]; + const name = Deno.args[2]; + const sceneOpts = parseArgs(Deno.args.slice(2)); + + if (action === "install" && name) { + const local = sceneOpts.local; + if (local === true) { + console.error("[dimsim] --local requires a path: dimsim scene install apt --local ./scene-apt-v0.1.0.tar.gz"); + Deno.exit(1); + } + await sceneInstall(name, local as string | undefined); + } else if (action === "list") { + await sceneList(); + } else if (action === "remove" && name) { + await sceneRemove(name); + } else { + console.log("Usage:"); + console.log(" dimsim scene install [--local ]"); + console.log(" dimsim scene list"); + console.log(" dimsim scene remove "); + } + Deno.exit(0); + } + + // ── List objects ──────────────────────────────────────────────────── + if (subcommand === "list") { + const what = Deno.args[1]; + if (what === "objects") { + const listOpts = parseArgs(Deno.args.slice(2)); + const sceneName = listOpts.scene as string; + if (!sceneName) { + console.error("[dimsim] --scene is required. Example: dimsim list objects --scene apt"); + Deno.exit(1); + } + + const distDir = await resolveDistDir(); + const scenePath = `${distDir}/sims/${sceneName}.json`; + try { + await Deno.stat(scenePath); + } catch { + console.error(`[dimsim] Scene "${sceneName}" not found at ${scenePath}`); + console.error(`[dimsim] Run 'dimsim scene install ${sceneName}' first.`); + Deno.exit(1); + } + + const index = loadSceneIndex(scenePath, sceneName); + const search = listOpts.search as string | undefined; + + let filtered = index.objects; + if (search) { + const lower = search.toLowerCase(); + filtered = index.objects.filter( + (o) => o.title.toLowerCase().includes(lower) || o.id.toLowerCase().includes(lower), + ); + console.log(`\nObjects matching "${search}" in scene "${sceneName}" (${filtered.length}):\n`); + } else { + console.log(`\nObjects in scene "${sceneName}" (${filtered.length} titled assets):\n`); + } + + if (filtered.length === 0) { + console.log(" (none)"); + } else { + const maxTitle = Math.min(45, Math.max(...filtered.map((o) => o.title.length))); + for (const obj of filtered) { + const t = obj.title.padEnd(maxTitle); + console.log(` ${t} (${obj.position.x}, ${obj.position.y}, ${obj.position.z})`); + } + } + console.log(); + Deno.exit(0); + } + console.log("Usage: dimsim list objects --scene [--search ]"); + Deno.exit(1); + } + + // ── Build eval ───────────────────────────────────────────────────── + if (subcommand === "build") { + const what = Deno.args[1]; + if (what === "eval") { + const buildOpts = parseArgs(Deno.args.slice(2)); + const sceneName = buildOpts.scene as string; + const target = buildOpts.target as string; + + if (!sceneName || !target) { + console.error("[dimsim] --scene and --target are required."); + console.error("Example: dimsim build eval --scene apt --target television"); + Deno.exit(1); + } + + const distDir = await resolveDistDir(); + const scenePath = `${distDir}/sims/${sceneName}.json`; + try { + await Deno.stat(scenePath); + } catch { + console.error(`[dimsim] Scene "${sceneName}" not found at ${scenePath}`); + console.error(`[dimsim] Run 'dimsim scene install ${sceneName}' first.`); + Deno.exit(1); + } + + try { + const result = buildEval({ + scenePath, + sceneName, + target, + threshold: buildOpts.threshold ? parseFloat(buildOpts.threshold as string) : undefined, + timeout: buildOpts.timeout ? parseInt(buildOpts.timeout as string) : undefined, + task: buildOpts.task as string | undefined, + name: buildOpts.name as string | undefined, + env: buildOpts.env as string | undefined, + evalsDir: EVALS_DIR, + }); + + console.log(`\nCreated eval: ${result.filePath}`); + console.log(` Task: "${result.task}"`); + console.log(` Target: ${result.targetTitle} (${result.targetPosition.x}, ${result.targetPosition.y}, ${result.targetPosition.z})`); + console.log(` Threshold: ${result.threshold}m`); + console.log(` Timeout: ${result.timeout}s`); + console.log(`\nRun: dimsim eval --connect --env ${result.env} --workflow ${result.workflowName}\n`); + } catch (err: any) { + console.error(`[dimsim] ${err.message}`); + Deno.exit(1); + } + Deno.exit(0); + } + console.log("Usage: dimsim build eval --scene --target [options]"); + Deno.exit(1); + } + + // ── Dev ───────────────────────────────────────────────────────────── + if (subcommand === "dev") { + const distDir = await resolveDistDir(); + const scene = (opts.scene as string) || "apt"; + const headless = opts.headless === true; + const render = ((opts.render as string) === "cpu" ? "cpu" : "gpu") as RenderMode; + const numChannels = Math.max(1, parseInt(opts.channels as string) || 1); + const evalWorkflow = opts.eval as string | undefined; + + // Sensor publish rates (ms) — overrides browser defaults + const sensorRates: Record = {}; + if (opts["image-rate"]) sensorRates.images = parseInt(opts["image-rate"] as string); + if (opts["lidar-rate"]) sensorRates.lidar = parseInt(opts["lidar-rate"] as string); + if (opts["odom-rate"]) sensorRates.odom = parseInt(opts["odom-rate"] as string); + + // Sensor enable/disable (depth only — color and lidar are essential) + const sensorEnable: Record = {}; + if (opts["no-depth"] === true) sensorEnable.depth = false; + + // Camera FOV + const cameraFov = opts["camera-fov"] ? parseInt(opts["camera-fov"] as string) : undefined; + + // Build channel list for multi-instance mode + const channels = numChannels > 1 + ? Array.from({ length: numChannels }, (_, i) => `page-${i}`) + : undefined; + + console.log(`[dimsim] Dev mode — scene: ${scene}, port: ${port}${headless ? " (headless)" : ""}${channels ? ` (${numChannels} channels)` : ""}`); + console.log(`[dimsim] Serving from: ${distDir}`); + + // LCM bridge is always active in dev mode (unlike eval --headless which disables it) + startBridgeServer({ + port, distDir, scene, headless, channels, + sensorRates: Object.keys(sensorRates).length > 0 ? sensorRates : undefined, + sensorEnable: Object.keys(sensorEnable).length > 0 ? sensorEnable : undefined, + cameraFov, + }); + + if (headless) { + if (channels) { + // Multi-page mode: open N browser pages in one Chromium instance + console.log(`[dimsim] Launching headless browser with ${numChannels} pages...`); + const url = `http://localhost:${port}`; + await launchMultiPage({ url, numPages: numChannels, render, timeout: 120_000 }); + await new Promise((r) => setTimeout(r, 3000)); + console.log(`[dimsim] ${numChannels} headless pages ready. LCM bridge active.`); + } else { + console.log("[dimsim] Launching headless browser..."); + const url = `http://localhost:${port}`; + // CPU rendering with SwiftShader is slow — scene + agent init takes + // ~27s on CI Macs. Allow 90s by default; override via env var. + const headlessTimeout = parseInt( + Deno.env.get("DIMSIM_HEADLESS_TIMEOUT") || "90000", + ); + await launchHeadless({ url, timeout: headlessTimeout, render }); + await new Promise((r) => setTimeout(r, 3000)); + console.log("[dimsim] Headless browser ready. LCM bridge active."); + } + } else { + console.log(`[dimsim] Open http://localhost:${port} in your browser`); + } + + if (evalWorkflow) { + console.log(`[dimsim] Eval workflow: ${evalWorkflow}`); + console.log("[dimsim] Waiting for browser to connect and load scene...\n"); + + const wsUrl = `ws://localhost:${port}`; + const manifestPath = resolve(EVALS_DIR, "manifest.json"); + + const results = await runEvals({ + wsUrl, + manifestPath, + filterEnv: opts.env as string, + filterWorkflow: evalWorkflow, + outputFormat: "json", + }); + + const passed = results.filter((r) => r.pass).length; + const failed = results.length - passed; + console.log(`\n[dimsim] Eval done: ${passed} passed, ${failed} failed`); + + // Stay alive in dev mode (don't exit like headless eval does) + console.log("[dimsim] Eval complete. Server still running. Press Ctrl+C to stop."); + } else { + console.log("[dimsim] Press Ctrl+C to stop."); + } + + // Keep alive + await new Promise(() => {}); + } + + // ── Agent ─────────────────────────────────────────────────────────── + if (subcommand === "agent") { + if (IS_REMOTE && !opts.venv) { + console.error(`[dimsim] Agent mode requires a local dimos install.`); + console.error(`[dimsim] Pass --venv /path/to/python`); + Deno.exit(1); + } + const pythonBin = (opts.venv as string) || DIMOS_VENV!; + const navOnly = opts["nav-only"] === true; + + if (IS_REMOTE && !AGENT_PY) { + console.error(`[dimsim] Agent mode is only available when running from source.`); + Deno.exit(1); + } + + // Verify python exists + try { + await Deno.stat(pythonBin); + } catch { + console.error(`[dimsim] dimos venv not found at: ${pythonBin}`); + console.error(`[dimsim] Install dimos first, or pass --venv /path/to/python`); + Deno.exit(1); + } + + const cmd = [pythonBin, AGENT_PY!]; + if (navOnly) cmd.push("--nav-only"); + + console.log(`[dimsim] Starting dimos agent${navOnly ? " (nav-only)" : ""}...`); + console.log(`[dimsim] Python: ${pythonBin}`); + + const proc = new Deno.Command(cmd[0], { + args: cmd.slice(1), + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + env: { ...Deno.env.toObject() }, + }).spawn(); + + const status = await proc.status; + Deno.exit(status.code); + } + + // ── Eval list ─────────────────────────────────────────────────────── + if (subcommand === "eval" && Deno.args[1] === "list") { + const evalsDir = EVALS_DIR; + const envs: Map = new Map(); + + try { + for await (const entry of Deno.readDir(evalsDir)) { + if (!entry.isDirectory) continue; + const workflows: string[] = []; + for await (const file of Deno.readDir(`${evalsDir}/${entry.name}`)) { + if (file.name.endsWith(".json") && file.name !== "manifest.json") { + workflows.push(file.name.replace(".json", "")); + } + } + if (workflows.length > 0) { + workflows.sort(); + envs.set(entry.name, workflows); + } + } + } catch { + console.log("\nNo evals installed. Run 'dimsim setup' or 'dimsim eval create' first.\n"); + Deno.exit(0); + } + + if (envs.size === 0) { + console.log("\nNo eval workflows found.\n"); + Deno.exit(0); + } + + const sorted = [...envs.entries()].sort((a, b) => a[0].localeCompare(b[0])); + let total = 0; + console.log(""); + for (const [env, workflows] of sorted) { + console.log(` \x1b[1m${env}\x1b[0m \x1b[2m(${workflows.length})\x1b[0m`); + for (const w of workflows) { + console.log(` ${w}`); + total++; + } + } + console.log(`\n \x1b[2m${total} workflow(s) across ${envs.size} environment(s)\x1b[0m\n`); + Deno.exit(0); + } + + // ── Eval create (interactive wizard) ───────────────────────────────── + if (subcommand === "eval" && Deno.args[1] === "create") { + // ANSI helpers + const c = { + bold: (s: string) => `\x1b[1m${s}\x1b[0m`, + cyan: (s: string) => `\x1b[36m${s}\x1b[0m`, + green: (s: string) => `\x1b[32m${s}\x1b[0m`, + yellow: (s: string) => `\x1b[33m${s}\x1b[0m`, + red: (s: string) => `\x1b[31m${s}\x1b[0m`, + dim: (s: string) => `\x1b[2m${s}\x1b[0m`, + }; + + const distDir = await resolveDistDir(); + const simsDir = `${distDir}/sims`; + + // ── 1. Pick scene ────────────────────────────────────────────────── + const installed: string[] = []; + try { + for await (const entry of Deno.readDir(simsDir)) { + if (entry.name.endsWith(".json") && entry.name !== "manifest.json") { + installed.push(entry.name.replace(".json", "")); + } + } + } catch { /* no sims */ } + installed.sort(); + + if (installed.length === 0) { + console.error(c.red("No scenes installed. Run 'dimsim scene install ' first.")); + Deno.exit(1); + } + + console.log(`\n${c.bold(" Create Eval Workflow")}\n`); + console.log(c.cyan(" Installed scenes:")); + installed.forEach((s, i) => console.log(` ${c.dim(`${i + 1}.`)} ${s}`)); + + let sceneName = ""; + let scenePath = ""; + while (true) { + const input = prompt(`\n ${c.cyan("Scene")} ${c.dim(`[${installed[0]}]`)}:`) || installed[0]; + const resolved = installed.includes(input) + ? input + : installed[parseInt(input) - 1]; + if (resolved) { + sceneName = resolved; + scenePath = `${simsDir}/${sceneName}.json`; + try { + await Deno.stat(scenePath); + console.log(` ${c.green("→")} ${sceneName}`); + break; + } catch { /* fall through */ } + } + console.log(c.yellow(` "${input}" not found. Pick a number or name from the list above.`)); + } + + // ── 2. Pick rubric ───────────────────────────────────────────────── + const rubricChoices = [ + { key: "objectDistance", label: "objectDistance", desc: "agent must reach a target object" }, + { key: "llmJudge", label: "llmJudge", desc: "VLM judges success from screenshots" }, + { key: "groundTruth", label: "groundTruth", desc: "check spatial ground truth conditions" }, + ]; + + console.log(`\n${c.cyan(" Rubric types:")}`); + rubricChoices.forEach((r, i) => + console.log(` ${c.dim(`${i + 1}.`)} ${c.bold(r.label)} ${c.dim(`— ${r.desc}`)}`) + ); + + let rubric = ""; + while (true) { + const input = prompt(`\n ${c.cyan("Rubric")} ${c.dim("[1]")}:`) || "1"; + const byNum = rubricChoices[parseInt(input) - 1]; + const byName = rubricChoices.find((r) => r.key === input); + const match = byNum || byName; + if (match) { + rubric = match.key; + console.log(` ${c.green("→")} ${match.label}`); + break; + } + console.log(c.yellow(` Invalid choice. Enter 1-3 or a rubric name.`)); + } + + // ── 3. Pick target object (objectDistance needs it) ───────────────── + const needsTarget = rubric === "objectDistance"; + const index = loadSceneIndex(scenePath, sceneName); + let target = ""; + let matchedObj: ReturnType = null; + + if (needsTarget) { + console.log(`\n${c.cyan(` Objects in "${sceneName}"`)} ${c.dim(`(${index.objects.length})`)}:`); + const sample = index.objects.slice(0, 20); + for (const obj of sample) { + console.log(` ${obj.title}`); + } + if (index.objects.length > 20) { + console.log(c.dim(` ... and ${index.objects.length - 20} more (dimsim list objects --scene ${sceneName})`)); + } + + while (true) { + const input = prompt(`\n ${c.cyan("Target object")}:`); + if (!input) { + console.log(c.yellow(" Target is required for objectDistance rubric.")); + continue; + } + matchedObj = findObject(input, index); + if (matchedObj) { + target = input; + console.log(` ${c.green("→")} "${matchedObj.title}" at (${matchedObj.position.x}, ${matchedObj.position.y}, ${matchedObj.position.z})`); + break; + } + const suggestions = suggestObjects(input, index); + if (suggestions.length > 0) { + console.log(c.yellow(` No match for "${input}". Similar: ${suggestions.join(", ")}`)); + } else { + console.log(c.yellow(` No match for "${input}". Try 'dimsim list objects --scene ${sceneName}'.`)); + } + } + } + + // ── 4. Task prompt ───────────────────────────────────────────────── + const defaultTask = needsTarget && matchedObj + ? `Go to the ${matchedObj.title}` + : ""; + let task = ""; + while (true) { + const suffix = defaultTask ? ` ${c.dim(`[${defaultTask}]`)}` : ""; + const input = prompt(`\n ${c.cyan("Task prompt")}${suffix}:`) || defaultTask; + if (input) { + task = input; + break; + } + console.log(c.yellow(" Task prompt is required.")); + } + + // ── 5. Rubric-specific config ────────────────────────────────────── + let threshold = 2.0; + let llmPrompt = ""; + + if (rubric === "objectDistance") { + while (true) { + const input = prompt(` ${c.cyan("Distance threshold")} ${c.dim("[2.0m]")}:`) || "2.0"; + const val = parseFloat(input); + if (!isNaN(val) && val > 0) { + threshold = val; + break; + } + console.log(c.yellow(" Enter a positive number (meters).")); + } + } else if (rubric === "llmJudge") { + const defaultJudge = `Did the agent successfully complete: ${task}?`; + llmPrompt = prompt(` ${c.cyan("LLM judge prompt")} ${c.dim(`[${defaultJudge}]`)}:`) || defaultJudge; + } + + // ── 6. Eval name ─────────────────────────────────────────────────── + const slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); + const defaultName = target ? slug(target) : slug(task.slice(0, 40)); + const name = prompt(` ${c.cyan("Eval name")} ${c.dim(`[${defaultName}]`)}:`) || defaultName; + + // ── 7. Timeout ───────────────────────────────────────────────────── + let timeout = 60; + while (true) { + const input = prompt(` ${c.cyan("Timeout")} ${c.dim("[60s]")}:`) || "60"; + const val = parseInt(input); + if (!isNaN(val) && val > 0) { + timeout = val; + break; + } + console.log(c.yellow(" Enter a positive number (seconds).")); + } + + // ── Build & write ────────────────────────────────────────────────── + const successCriteria: Record = {}; + if (rubric === "objectDistance") { + successCriteria.objectDistance = { object: "agent", target, thresholdM: threshold }; + } else if (rubric === "llmJudge") { + successCriteria.llmJudge = { prompt: llmPrompt }; + } else if (rubric === "groundTruth") { + successCriteria.groundTruth = {}; + } + + const env = sceneName; + const workflow = { + name, + environment: env, + task, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + timeoutSec: timeout, + successCriteria, + }; + + const envDir = `${EVALS_DIR}/${env}`; + try { Deno.mkdirSync(envDir, { recursive: true }); } catch { /* exists */ } + const filePath = `${envDir}/${name}.json`; + Deno.writeTextFileSync(filePath, JSON.stringify(workflow, null, 2) + "\n"); + + console.log(`\n ${c.green("Created:")} ${filePath}`); + console.log(`\n ${c.cyan("Run it:")}`); + console.log(` dimsim eval --connect --env ${env} --workflow ${name}`); + console.log(` dimsim eval --headless --env ${env} --workflow ${name}\n`); + Deno.exit(0); + } + + // ── Eval ──────────────────────────────────────────────────────────── + if (subcommand === "eval") { + const connectMode = opts.connect === true; + const outputFormat = (opts.output as string) === "junit" ? "junit" : "json"; + const manifestPath = resolve(EVALS_DIR, "manifest.json"); + + // --connect mode: just run the eval runner against an existing bridge + if (connectMode) { + const wsUrl = `ws://localhost:${port}`; + console.log(`[dimsim] Connecting to existing bridge at ${wsUrl}...`); + + const results = await runEvals({ + wsUrl, + manifestPath, + filterEnv: opts.env as string, + filterWorkflow: opts.workflow as string, + outputFormat: outputFormat as "json" | "junit", + }); + + const passed = results.filter((r) => r.pass).length; + const failed = results.length - passed; + console.log(`\n[dimsim] Done: ${passed} passed, ${failed} failed, ${results.length} total`); + Deno.exit(failed > 0 ? 1 : 0); + } + + const distDir = await resolveDistDir(); + const headless = opts.headless === true; + const scene = (opts.scene as string) || (opts.env as string) || "apt"; + const parallel = Math.max(1, parseInt(opts.parallel as string) || 1); + const render = ((opts.render as string) === "gpu" ? "gpu" : "cpu") as RenderMode; + const defaultTimeout = render === "cpu" ? 120000 : 30000; + const timeout = parseInt(opts.timeout as string) || defaultTimeout; + + if (headless && parallel > 1) { + const allWorkflows = collectWorkflows( + manifestPath, + opts.env as string, + opts.workflow as string, + ); + + if (allWorkflows.length === 0) { + console.log("[dimsim] No workflows match filter criteria."); + Deno.exit(0); + } + + const numPages = Math.min(parallel, allWorkflows.length); + console.log(`[dimsim] Multi-page eval — ${allWorkflows.length} workflows across ${numPages} page(s)`); + + startBridgeServer({ port, distDir, scene, evalOnly: true }); + await new Promise((r) => setTimeout(r, 500)); + + const url = `http://localhost:${port}`; + const instance = await launchMultiPage({ url, numPages, timeout, render }); + await new Promise((r) => setTimeout(r, 2000)); + + const allResults = await runEvalsMultiPage({ + wsUrl: `ws://localhost:${port}`, + manifestPath, + channels: instance.channels, + filterEnv: opts.env as string, + filterWorkflow: opts.workflow as string, + }); + + await instance.close(); + + if (outputFormat === "junit") { + console.log(toJunitXml(allResults)); + } else { + console.log(JSON.stringify(allResults, null, 2)); + } + + const passed = allResults.filter((r) => r.pass).length; + const failed = allResults.length - passed; + console.log(`\n[dimsim] Done: ${passed} passed, ${failed} failed, ${allResults.length} total`); + Deno.exit(failed > 0 ? 1 : 0); + } + + // -- Single worker eval (sequential) ----------------------------------- + console.log(`[dimsim] Eval mode — headless: ${headless}, port: ${port}`); + + startBridgeServer({ port, distDir, scene, evalOnly: headless }); + await new Promise((r) => setTimeout(r, 500)); + + const url = `http://localhost:${port}`; + + if (headless) { + console.log("[dimsim] Launching headless browser..."); + const instance = await launchHeadless({ url, timeout, render }); + await new Promise((r) => setTimeout(r, 3000)); + + const results = await runEvals({ + wsUrl: `ws://localhost:${port}`, + manifestPath, + filterEnv: opts.env as string, + filterWorkflow: opts.workflow as string, + outputFormat: outputFormat as "json" | "junit", + }); + + await instance.close(); + + const failed = results.filter((r) => !r.pass).length; + Deno.exit(failed > 0 ? 1 : 0); + } else { + console.log(`[dimsim] Open ${url} in your browser to start evals`); + console.log("[dimsim] Press Ctrl+C to stop."); + await new Promise(() => {}); + } + } + + printUsage(); + Deno.exit(1); +} + +main(); diff --git a/misc/DimSim/dimos-cli/deno.json b/misc/DimSim/dimos-cli/deno.json new file mode 100644 index 0000000000..a2c8e98d6f --- /dev/null +++ b/misc/DimSim/dimos-cli/deno.json @@ -0,0 +1,37 @@ +{ + "name": "@antim/dimsim", + "version": "0.2.2", + "description": "3D simulation environment for the dimos robotics stack. Browser-based Three.js + Rapier sim with LCM transport, sensor publishing, and eval harness.", + "license": "MIT", + "exports": { + ".": "./cli.ts", + "./mod": "./mod.ts" + }, + "publish": { + "include": [ + "README.md", + "cli.ts", + "mod.ts", + "bridge/", + "eval/", + "headless/", + "vendor/", + "setup.ts" + ], + "exclude": [ + "test/" + ] + }, + "tasks": { + "start": "deno run --allow-net --allow-read --unstable-net bridge/server.ts", + "eval": "deno run --allow-all cli.ts eval" + }, + "imports": { + "@dimos/lcm": "./vendor/lcm/mod.ts", + "@dimos/msgs": "jsr:@dimos/msgs@^0.1.4", + "@std/path": "jsr:@std/path@^1", + "@std/http": "jsr:@std/http@^1", + "playwright": "npm:playwright@^1.58", + "@dimforge/rapier3d-compat": "npm:@dimforge/rapier3d-compat@^0.14.0" + } +} diff --git a/misc/DimSim/dimos-cli/deno.lock b/misc/DimSim/dimos-cli/deno.lock new file mode 100644 index 0000000000..7cbc0c61ac --- /dev/null +++ b/misc/DimSim/dimos-cli/deno.lock @@ -0,0 +1,117 @@ +{ + "version": "5", + "specifiers": { + "jsr:@antim/dimsim@*": "0.1.27", + "jsr:@dimos/msgs@~0.1.4": "0.1.4", + "jsr:@std/cli@^1.0.27": "1.0.27", + "jsr:@std/encoding@^1.0.10": "1.0.10", + "jsr:@std/fmt@^1.0.9": "1.0.9", + "jsr:@std/fs@^1.0.22": "1.0.22", + "jsr:@std/html@^1.0.5": "1.0.5", + "jsr:@std/http@1": "1.0.24", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/media-types@^1.1.0": "1.1.0", + "jsr:@std/net@^1.0.6": "1.0.6", + "jsr:@std/path@1": "1.1.4", + "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/streams@^1.0.17": "1.0.17", + "npm:@dimforge/rapier3d-compat@0.14": "0.14.0", + "npm:playwright@*": "1.58.2", + "npm:playwright@^1.58.0": "1.58.2" + }, + "jsr": { + "@antim/dimsim@0.1.27": { + "integrity": "fc7f8f3aaeb0e06b06aaf35e157308f656be344db434a10bb573343c43a4bbbb", + "dependencies": [ + "jsr:@dimos/msgs", + "jsr:@std/http", + "jsr:@std/path@1", + "npm:@dimforge/rapier3d-compat", + "npm:playwright@^1.58.0" + ] + }, + "@dimos/msgs@0.1.4": { + "integrity": "564bc30b4bc41a562c296c257a15055283ca0cbd66d0627991ede5295832d0c4" + }, + "@std/cli@1.0.27": { + "integrity": "eba97edd0891871a7410e835dd94b3c260c709cca5983df2689c25a71fbe04de" + }, + "@std/encoding@1.0.10": { + "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" + }, + "@std/fmt@1.0.9": { + "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" + }, + "@std/fs@1.0.22": { + "integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308" + }, + "@std/html@1.0.5": { + "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" + }, + "@std/http@1.0.24": { + "integrity": "4dd59afd7cfd6e2e96e175b67a5a829b449ae55f08575721ec691e5d85d886d4", + "dependencies": [ + "jsr:@std/cli", + "jsr:@std/encoding", + "jsr:@std/fmt", + "jsr:@std/fs", + "jsr:@std/html", + "jsr:@std/media-types", + "jsr:@std/net", + "jsr:@std/path@^1.1.4", + "jsr:@std/streams" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/net@1.0.6": { + "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/streams@1.0.17": { + "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" + } + }, + "npm": { + "@dimforge/rapier3d-compat@0.14.0": { + "integrity": "sha512-/uHrUzS+CRQ+NQrrJCEDUkhwHlNsAAexbNXgbN9sHY+GwR+SFFAFrxRr8Llf5/AJZzqiLANdQIfJ63Cw4gJVqw==" + }, + "fsevents@2.3.2": { + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "os": ["darwin"], + "scripts": true + }, + "playwright-core@1.58.2": { + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "bin": true + }, + "playwright@1.58.2": { + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dependencies": [ + "playwright-core" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + } + }, + "workspace": { + "dependencies": [ + "jsr:@dimos/msgs@~0.1.4", + "jsr:@std/http@1", + "jsr:@std/path@1", + "npm:@dimforge/rapier3d-compat@0.14", + "npm:playwright@^1.58.0" + ] + } +} diff --git a/misc/DimSim/dimos-cli/eval/builder.ts b/misc/DimSim/dimos-cli/eval/builder.ts new file mode 100644 index 0000000000..99888aa528 --- /dev/null +++ b/misc/DimSim/dimos-cli/eval/builder.ts @@ -0,0 +1,123 @@ +/** + * Eval Builder — generate workflow JSON from validated inputs. + * + * An eval = rubric + target + timeout. This module validates the target + * exists in the scene, generates the workflow JSON, and updates the manifest. + */ + +import { loadSceneIndex, findObject, findAllObjects, suggestObjects } from "./scene-index.ts"; + +export interface BuildEvalOptions { + scenePath: string; + sceneName: string; + target: string; + threshold?: number; + timeout?: number; + task?: string; + name?: string; + env?: string; + evalsDir: string; +} + +export interface BuildResult { + filePath: string; + workflowName: string; + task: string; + targetTitle: string; + targetPosition: { x: number; y: number; z: number }; + threshold: number; + timeout: number; + env: string; +} + +function slugify(s: string): string { + return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); +} + +export function buildEval(opts: BuildEvalOptions): BuildResult { + const index = loadSceneIndex(opts.scenePath, opts.sceneName); + + // Validate target exists in scene + const match = findObject(opts.target, index); + if (!match) { + const suggestions = suggestObjects(opts.target, index); + let msg = `No asset matching "${opts.target}" in scene "${opts.sceneName}".`; + if (suggestions.length > 0) { + msg += `\nSimilar: ${suggestions.join(", ")}`; + } + msg += `\nHint: dimsim list objects --scene ${opts.sceneName}`; + throw new Error(msg); + } + + // Warn about duplicates + const allMatches = findAllObjects(opts.target, index); + if (allMatches.length > 1) { + console.warn( + `[build] Warning: "${opts.target}" matches ${allMatches.length} objects. ` + + `Using first: "${match.title}" at (${match.position.x}, ${match.position.y}, ${match.position.z})` + ); + } + + const threshold = opts.threshold ?? 2.0; + const timeout = opts.timeout ?? 60; + const env = opts.env || opts.sceneName; + const workflowName = opts.name || slugify(opts.target); + const task = opts.task || `Go to the ${match.title}`; + + const workflow = { + name: workflowName, + environment: env, + task, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + timeoutSec: timeout, + successCriteria: { + objectDistance: { + object: "agent", + target: opts.target, + thresholdM: threshold, + }, + }, + }; + + // Write workflow JSON + const envDir = `${opts.evalsDir}/${env}`; + try { Deno.mkdirSync(envDir, { recursive: true }); } catch { /* exists */ } + const filePath = `${envDir}/${workflowName}.json`; + Deno.writeTextFileSync(filePath, JSON.stringify(workflow, null, 2) + "\n"); + + // Update manifest + updateManifest(`${opts.evalsDir}/manifest.json`, env, opts.sceneName, workflowName); + + return { + filePath, + workflowName, + task, + targetTitle: match.title, + targetPosition: match.position, + threshold, + timeout, + env, + }; +} + +function updateManifest(manifestPath: string, env: string, scene: string, workflowName: string): void { + let manifest: { version: string; environments: { name: string; scene: string; workflows: string[] }[] }; + + try { + manifest = JSON.parse(Deno.readTextFileSync(manifestPath)); + } catch { + manifest = { version: "1.0", environments: [] }; + } + + let envEntry = manifest.environments.find((e) => e.name === env); + if (!envEntry) { + envEntry = { name: env, scene, workflows: [] }; + manifest.environments.push(envEntry); + } + + if (!envEntry.workflows.includes(workflowName)) { + envEntry.workflows.push(workflowName); + } + + Deno.writeTextFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n"); +} diff --git a/misc/DimSim/dimos-cli/eval/runner.ts b/misc/DimSim/dimos-cli/eval/runner.ts new file mode 100644 index 0000000000..b7dbd8d39e --- /dev/null +++ b/misc/DimSim/dimos-cli/eval/runner.ts @@ -0,0 +1,389 @@ +/** + * Eval Runner — Deno-side orchestrator that drives eval workflows. + * + * Connects to the bridge server via WebSocket and sends commands to the + * browser's EvalHarness: load environment, start workflow, collect results. + * + * Two modes: + * runEvals() — Sequential: one page, one workflow at a time + * runEvalsMultiPage() — Parallel: N pages in one browser, channel-routed + */ + +export interface EvalResult { + name: string; + environment: string; + reason: string; + durationMs: number; + rubricScores: Record; + pass: boolean; +} + +export interface RunEvalOptions { + wsUrl: string; + manifestPath: string; + filterEnv?: string; + filterWorkflow?: string; + /** Run only these specific workflow names (overrides filterWorkflow). */ + filterWorkflows?: string[]; + outputFormat?: "json" | "junit"; +} + +export interface WorkflowEntry { + env: string; + scene: string; + workflowPath: string; + workflowName: string; +} + +/** Collect workflows from manifest, applying filters. */ +export function collectWorkflows(manifestPath: string, filterEnv?: string, filterWorkflow?: string, filterWorkflows?: string[]): WorkflowEntry[] { + const manifestText = Deno.readTextFileSync(manifestPath); + const manifest = JSON.parse(manifestText); + const result: WorkflowEntry[] = []; + + for (const env of manifest.environments) { + if (filterEnv && env.name !== filterEnv) continue; + for (const wfName of env.workflows) { + if (filterWorkflows) { + if (!filterWorkflows.includes(wfName)) continue; + } else if (filterWorkflow && wfName !== filterWorkflow) { + continue; + } + const dir = new URL(`../../evals/${env.name}/`, import.meta.url).pathname; + result.push({ + env: env.name, + scene: env.scene, + workflowPath: `${dir}${wfName}.json`, + workflowName: wfName, + }); + } + } + return result; +} + +export async function runEvals(options: RunEvalOptions): Promise { + const { wsUrl, manifestPath, filterEnv, filterWorkflow, filterWorkflows, outputFormat } = options; + + const workflowsToRun = collectWorkflows(manifestPath, filterEnv, filterWorkflow, filterWorkflows); + + if (workflowsToRun.length === 0) { + console.log("[runner] No workflows match filter criteria."); + return []; + } + + console.log(`[runner] Running ${workflowsToRun.length} workflow(s)...`); + + // Connect to bridge WebSocket + const ws = new WebSocket(wsUrl); + ws.binaryType = "arraybuffer"; + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("WebSocket connect timeout")), 30000); + ws.onopen = () => { clearTimeout(timeout); resolve(); }; + ws.onerror = (e) => { clearTimeout(timeout); reject(new Error(`WebSocket connection failed: ${e}`)); }; + }); + + console.log("[runner] Connected to bridge"); + + // Helper: send command and wait for response + function sendAndWait(cmd: Record, responseType: string, timeoutMs = 60000): Promise> { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error(`Timeout waiting for ${responseType}`)), timeoutMs); + + const handler = (event: MessageEvent) => { + if (typeof event.data !== "string") return; + try { + const msg = JSON.parse(event.data); + if (msg.type === responseType) { + clearTimeout(timeout); + ws.removeEventListener("message", handler); + resolve(msg); + } + } catch { /* not JSON */ } + }; + + ws.addEventListener("message", handler); + ws.send(JSON.stringify(cmd)); + }); + } + + // Wait for the browser eval harness to be alive (ping/pong handshake) + console.log("[runner] Waiting for browser eval harness..."); + const harnessTimeout = 60000; + const harnessStart = Date.now(); + let harnessReady = false; + while (Date.now() - harnessStart < harnessTimeout) { + try { + await sendAndWait({ type: "ping" }, "pong", 3000); + harnessReady = true; + break; + } catch { + // No response yet — browser not connected or harness not initialized + await new Promise((r) => setTimeout(r, 1000)); + } + } + if (!harnessReady) { + console.error("[runner] Timeout waiting for browser eval harness. Is the browser open?"); + ws.close(); + return []; + } + console.log("[runner] Browser eval harness connected!"); + + const results: EvalResult[] = []; + let currentScene = ""; + + for (const wf of workflowsToRun) { + // Load environment if different from current + if (wf.scene !== currentScene) { + console.log(`[runner] Loading environment: ${wf.env} (scene: ${wf.scene})`); + await sendAndWait({ type: "loadEnv", scene: wf.scene }, "envReady", 120000); + currentScene = wf.scene; + // Wait for physics to settle + await new Promise((r) => setTimeout(r, 2000)); + } + + // Load workflow definition + const wfText = await Deno.readTextFile(wf.workflowPath); + const workflow = JSON.parse(wfText); + + console.log(`[runner] Starting workflow: ${wf.workflowName} — "${workflow.task}"`); + + // Start workflow and wait for completion + const timeoutMs = (workflow.timeoutSec || 120) * 1000 + 30000; // +30s buffer for slow renderers + const result = await sendAndWait( + { type: "startWorkflow", workflow }, + "workflowComplete", + timeoutMs, + ) as Record; + + const scores = result.rubricScores as Record || {}; + const allPass = Object.values(scores).every((s) => s.pass !== false); + + const evalResult: EvalResult = { + name: wf.workflowName, + environment: wf.env, + reason: result.reason as string, + durationMs: result.durationMs as number, + rubricScores: scores, + pass: allPass, + }; + + results.push(evalResult); + + const status = allPass ? "PASS" : "FAIL"; + console.log(`[runner] ${status}: ${wf.workflowName} (${evalResult.durationMs}ms)`); + } + + ws.close(); + + // Output results + if (outputFormat === "junit") { + const xml = toJunitXml(results); + console.log(xml); + } else { + console.log(JSON.stringify(results, null, 2)); + } + + // Summary + const passed = results.filter((r) => r.pass).length; + const failed = results.length - passed; + console.log(`\n[runner] Done: ${passed} passed, ${failed} failed, ${results.length} total`); + + return results; +} + +// ── Multi-page parallel runner ──────────────────────────────────────────── + +export interface RunEvalsMultiPageOptions { + wsUrl: string; + manifestPath: string; + /** Channel IDs matching the browser pages (e.g. ["page-0", "page-1"]) */ + channels: string[]; + filterEnv?: string; + filterWorkflow?: string; +} + +/** + * Run eval workflows in parallel across multiple browser pages within a + * single browser instance. One WebSocket connection to the bridge; commands + * are routed to pages via a `channel` field that each page's EvalHarness + * filters on. + */ +export async function runEvalsMultiPage(options: RunEvalsMultiPageOptions): Promise { + const { wsUrl, manifestPath, channels, filterEnv, filterWorkflow } = options; + const numPages = channels.length; + + const allWorkflows = collectWorkflows(manifestPath, filterEnv, filterWorkflow); + if (allWorkflows.length === 0) { + console.log("[runner] No workflows match filter criteria."); + return []; + } + + console.log(`[runner] Multi-page: ${allWorkflows.length} workflow(s) across ${numPages} page(s)`); + + // Connect to bridge + const ws = new WebSocket(wsUrl); + ws.binaryType = "arraybuffer"; + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("WebSocket connect timeout")), 30000); + ws.onopen = () => { clearTimeout(timeout); resolve(); }; + ws.onerror = (e) => { clearTimeout(timeout); reject(new Error(`WebSocket connection failed: ${e}`)); }; + }); + + console.log("[runner] Connected to bridge"); + + // Channel-aware sendAndWait: matches on BOTH message type AND channel + function sendAndWait( + cmd: Record, + responseType: string, + channel: string, + timeoutMs = 60000, + ): Promise> { + return new Promise((resolve, reject) => { + const timer = setTimeout( + () => reject(new Error(`Timeout waiting for ${responseType} on ${channel}`)), + timeoutMs, + ); + + const handler = (event: MessageEvent) => { + if (typeof event.data !== "string") return; + try { + const msg = JSON.parse(event.data); + if (msg.type === responseType && msg.channel === channel) { + clearTimeout(timer); + ws.removeEventListener("message", handler); + resolve(msg); + } + } catch { /* not JSON */ } + }; + + ws.addEventListener("message", handler); + ws.send(JSON.stringify({ ...cmd, channel })); + }); + } + + // Wait for all pages to be ready (ping/pong per channel) + console.log("[runner] Waiting for all pages to be ready..."); + for (const ch of channels) { + const start = Date.now(); + let ready = false; + while (Date.now() - start < 60000) { + try { + await sendAndWait({ type: "ping" }, "pong", ch, 3000); + ready = true; + break; + } catch { + await new Promise((r) => setTimeout(r, 1000)); + } + } + if (!ready) { + console.error(`[runner] Timeout waiting for page ${ch}`); + ws.close(); + return []; + } + console.log(`[runner] Page ${ch} ready`); + } + + // Distribute workflows round-robin across pages + const batches: WorkflowEntry[][] = Array.from({ length: numPages }, () => []); + allWorkflows.forEach((wf, i) => batches[i % numPages].push(wf)); + + // Run each page's batch concurrently + const pagePromises = batches.map(async (batch, i) => { + const ch = channels[i]; + const tag = `[${ch}]`; + const pageResults: EvalResult[] = []; + let currentScene = ""; + + for (const wf of batch) { + try { + // Load scene if needed + if (wf.scene !== currentScene) { + console.log(`${tag} Loading environment: ${wf.env} (scene: ${wf.scene})`); + await sendAndWait({ type: "loadEnv", scene: wf.scene }, "envReady", ch, 120000); + currentScene = wf.scene; + await new Promise((r) => setTimeout(r, 2000)); + } + + const wfText = await Deno.readTextFile(wf.workflowPath); + const workflow = JSON.parse(wfText); + + console.log(`${tag} Starting: ${wf.workflowName} — "${workflow.task}"`); + + const timeoutMs = (workflow.timeoutSec || 120) * 1000 + 30000; + const result = await sendAndWait( + { type: "startWorkflow", workflow }, + "workflowComplete", + ch, + timeoutMs, + ) as Record; + + const scores = result.rubricScores as Record || {}; + const allPass = Object.values(scores).every((s) => s.pass !== false); + + const evalResult: EvalResult = { + name: wf.workflowName, + environment: wf.env, + reason: result.reason as string, + durationMs: result.durationMs as number, + rubricScores: scores, + pass: allPass, + }; + pageResults.push(evalResult); + + const status = allPass ? "PASS" : "FAIL"; + console.log(`${tag} ${status}: ${wf.workflowName} (${evalResult.durationMs}ms)`); + } catch (err) { + console.error(`${tag} Error on ${wf.workflowName}: ${err}`); + pageResults.push({ + name: wf.workflowName, + environment: wf.env, + reason: `page error: ${err}`, + durationMs: 0, + rubricScores: {}, + pass: false, + }); + } + } + + return pageResults; + }); + + const allResults = (await Promise.all(pagePromises)).flat(); + ws.close(); + + // Summary + const passed = allResults.filter((r) => r.pass).length; + const failed = allResults.length - passed; + console.log(`\n[runner] Done: ${passed} passed, ${failed} failed, ${allResults.length} total`); + + return allResults; +} + +// ── JUnit XML output ────────────────────────────────────────────────────── + +export function toJunitXml(results: EvalResult[]): string { + const totalTime = results.reduce((s, r) => s + r.durationMs, 0) / 1000; + const failures = results.filter((r) => !r.pass).length; + + let xml = `\n`; + xml += `\n`; + xml += ` \n`; + + for (const r of results) { + const time = (r.durationMs / 1000).toFixed(1); + xml += ` \n`; + } else { + xml += `>\n`; + xml += ` ${JSON.stringify(r.rubricScores)}\n`; + xml += ` \n`; + } + } + + xml += ` \n`; + xml += `\n`; + return xml; +} diff --git a/misc/DimSim/dimos-cli/eval/scene-index.ts b/misc/DimSim/dimos-cli/eval/scene-index.ts new file mode 100644 index 0000000000..e27d2caec3 --- /dev/null +++ b/misc/DimSim/dimos-cli/eval/scene-index.ts @@ -0,0 +1,107 @@ +/** + * Scene Index — parse scene JSON and search for objects by name. + * + * Uses the same substring matching logic as the browser-side rubric + * (`_findObject` in rubrics.ts) so validation matches scoring exactly. + */ + +export interface SceneObject { + title: string; + id: string; + position: { x: number; y: number; z: number }; +} + +export interface SceneIndex { + sceneName: string; + objects: SceneObject[]; +} + +/** + * Load a scene JSON and extract all titled assets with positions. + * Skips assets without titles (structural delta patches, etc). + */ +export function loadSceneIndex(scenePath: string, sceneName: string): SceneIndex { + const text = Deno.readTextFileSync(scenePath); + const json = JSON.parse(text); + const objects: SceneObject[] = []; + + if (Array.isArray(json.assets)) { + for (const asset of json.assets) { + const title = asset.title; + if (!title || typeof title !== "string") continue; + const pos = asset.transform?.position || asset.transform || {}; + objects.push({ + title: title.trim(), + id: asset.id || "", + position: { + x: Math.round((pos.x || 0) * 10) / 10, + y: Math.round((pos.y || 0) * 10) / 10, + z: Math.round((pos.z || 0) * 10) / 10, + }, + }); + } + } + + // Sort by title for display + objects.sort((a, b) => a.title.localeCompare(b.title)); + return { sceneName, objects }; +} + +/** + * Find an object by name — same case-insensitive substring match as the rubric. + * Returns the first match (same as rubric behavior). + */ +export function findObject(searchTerm: string, index: SceneIndex): SceneObject | null { + const lower = searchTerm.toLowerCase(); + for (const obj of index.objects) { + if (obj.title.toLowerCase().includes(lower) || obj.id.toLowerCase().includes(lower)) { + return obj; + } + } + return null; +} + +/** + * Find ALL objects matching the search term (for duplicate warnings). + */ +export function findAllObjects(searchTerm: string, index: SceneIndex): SceneObject[] { + const lower = searchTerm.toLowerCase(); + return index.objects.filter( + (obj) => obj.title.toLowerCase().includes(lower) || obj.id.toLowerCase().includes(lower), + ); +} + +/** + * Suggest similar objects when search fails (simple substring overlap). + */ +export function suggestObjects(searchTerm: string, index: SceneIndex, limit = 5): string[] { + const lower = searchTerm.toLowerCase(); + const scored: { title: string; score: number }[] = []; + + for (const obj of index.objects) { + const t = obj.title.toLowerCase(); + // Score: longest common substring length + let best = 0; + for (let len = 1; len <= lower.length; len++) { + for (let start = 0; start + len <= lower.length; start++) { + if (t.includes(lower.substring(start, start + len))) { + best = len; + } + } + } + if (best > 0) scored.push({ title: obj.title, score: best }); + } + + scored.sort((a, b) => b.score - a.score); + // Deduplicate titles + const seen = new Set(); + const result: string[] = []; + for (const s of scored) { + if (!seen.has(s.title)) { + seen.add(s.title); + result.push(s.title); + if (result.length >= limit) break; + } + } + return result; +} diff --git a/misc/DimSim/dimos-cli/headless/launcher.ts b/misc/DimSim/dimos-cli/headless/launcher.ts new file mode 100644 index 0000000000..0a9a6c8875 --- /dev/null +++ b/misc/DimSim/dimos-cli/headless/launcher.ts @@ -0,0 +1,204 @@ +/** + * Headless Launcher — Playwright-based headless Chromium for CI/CD evals. + * + * Rendering modes: + * gpu — Metal/ANGLE (macOS, fast, max ~3 parallel pages) + * cpu — SwiftShader (Linux CI, no GPU needed, sequential only on <16 cores) + */ + +import { chromium, type Browser, type Page } from "playwright"; + +export type RenderMode = "gpu" | "cpu"; + +export interface LaunchOptions { + url: string; + timeout?: number; + render?: RenderMode; +} + +export interface HeadlessInstance { + browser: Browser; + page: Page; + close: () => Promise; +} + +export interface MultiPageInstance { + browser: Browser; + pages: Page[]; + channels: string[]; + close: () => Promise; +} + +export interface MultiPageOptions { + url: string; + numPages: number; + timeout?: number; + render?: RenderMode; +} + +// ── Chrome flags per render mode ────────────────────────────────────────── + +const GPU_ARGS = [ + "--headless=new", + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-features=SkiaGraphite", + "--enable-webgl", + "--enable-webgl2", + "--ignore-gpu-blocklist", + "--enable-gpu", + "--use-gl=angle", + "--use-angle=metal", + "--in-process-gpu", + "--disable-gpu-sandbox", + // Prevent Chrome from throttling timers in headless/background mode + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", +]; + +const CPU_ARGS = [ + "--headless=new", + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-features=SkiaGraphite", + "--enable-webgl", + "--enable-webgl2", + "--use-gl=angle", + "--use-angle=swiftshader", + "--enable-unsafe-swiftshader", + "--disable-gpu", + // Prevent Chrome from throttling timers in headless/background mode + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", +]; + +// Default: bundled Chromium (works on Linux + macOS in CPU mode with SwiftShader). +// Set DIMSIM_CHROME_CHANNEL=chrome to use system Google Chrome (needed for hardware +// WebGL on macOS — bundled Chromium ships without the full Metal/ANGLE GPU stack). +const LAUNCH_CHANNEL = Deno.env.get("DIMSIM_CHROME_CHANNEL") || undefined; + +// ── Helpers ─────────────────────────────────────────────────────────────── + +/** Filter noisy browser console output — only forward errors, warnings, and eval/bridge logs. */ +function hookPageConsole(page: Page, tag: string): void { + const verbose = Deno.env.get("DIMSIM_VERBOSE") === "1"; + page.on("console", (msg) => { + const type = msg.type(); + const text = msg.text(); + if (!verbose) { + if (text.includes("Texture marked for update") || text.includes("Failed to load resource") || + text.includes("GPU stall due to ReadPixels") || text.includes("Automatic fallback to software WebGL") || + text.includes("GroupMarkerNotSet")) return; + } + if (type === "error") console.error(`${tag} ${text}`); + else if (type === "warning") console.warn(`${tag} ${text}`); + else if (verbose || text.startsWith("[eval]") || text.startsWith("[DimosBridge]")) { + console.log(`${tag} ${text}`); + } + }); +} + +function getViewport(render: RenderMode) { + // CPU mode: tiny viewport — SwiftShader renders every pixel on CPU + return render === "cpu" + ? { width: 320, height: 240 } + : { width: 1280, height: 720 }; +} + +// ── Single-page launcher ───────────────────────────────────────────────── + +export async function launchHeadless(options: LaunchOptions): Promise { + const { url, timeout = 30000, render = "cpu" } = options; + const args = render === "gpu" ? GPU_ARGS : CPU_ARGS; + + console.log(`[headless] Launching: render=${render}`); + + const browser = await chromium.launch({ + headless: false, // --headless=new passed via args (Playwright's built-in headless uses old mode) + channel: LAUNCH_CHANNEL, + args, + }); + + const context = await browser.newContext({ viewport: getViewport(render), deviceScaleFactor: 1 }); + const page = await context.newPage(); + hookPageConsole(page, "[browser]"); + + // Set default timeout so waitForFunction picks it up (its third arg is + // options, not the second — passing {timeout} as second silently uses + // Playwright's 30s default). + context.setDefaultTimeout(timeout); + page.setDefaultTimeout(timeout); + + await page.goto(url, { waitUntil: "load", timeout }); + await page.waitForFunction( + () => typeof (window as unknown as Record).__dimosBridge !== "undefined", + undefined, + { timeout }, + ); + + console.log("[headless] Engine ready."); + + return { + browser, + page, + close: async () => { + await browser.close(); + console.log("[headless] Browser closed."); + }, + }; +} + +// ── Multi-page launcher (single browser, N tabs) ──────────────────────── + +export async function launchMultiPage(options: MultiPageOptions): Promise { + const { url, numPages, timeout = 120_000, render = "cpu" } = options; + const args = render === "gpu" ? GPU_ARGS : CPU_ARGS; + const viewport = getViewport(render); + + console.log(`[headless] Multi-page: ${numPages} pages, render=${render}, timeout=${timeout}ms`); + + const browser = await chromium.launch({ headless: false, channel: LAUNCH_CHANNEL, args }); + + const pages: Page[] = []; + const channels: string[] = []; + + for (let i = 0; i < numPages; i++) { + const channel = `page-${i}`; + channels.push(channel); + + const context = await browser.newContext({ viewport, deviceScaleFactor: 1 }); + context.setDefaultTimeout(timeout); + const page = await context.newPage(); + page.setDefaultTimeout(timeout); + hookPageConsole(page, `[page-${i}]`); + + const pageUrl = `${url}?channel=${channel}`; + console.log(`[headless] Page ${i}: loading...`); + await page.goto(pageUrl, { waitUntil: "load", timeout }); + await page.waitForFunction( + () => typeof (window as unknown as Record).__dimosBridge !== "undefined", + undefined, + { timeout }, + ); + console.log(`[headless] Page ${i}: ready`); + + pages.push(page); + + // Stagger launches to avoid GPU/CPU contention during scene load + if (i < numPages - 1) await new Promise((r) => setTimeout(r, 5000)); + } + + console.log(`[headless] All ${numPages} pages ready.`); + + return { + browser, + pages, + channels, + close: async () => { + await browser.close(); + console.log("[headless] Browser closed."); + }, + }; +} diff --git a/misc/DimSim/dimos-cli/mod.ts b/misc/DimSim/dimos-cli/mod.ts new file mode 100644 index 0000000000..ff096eae93 --- /dev/null +++ b/misc/DimSim/dimos-cli/mod.ts @@ -0,0 +1,86 @@ +/** + * @module + * + * **DimSim** — 3D simulation environment for the + * [dimos](https://github.com/dimensionalOS/dimos) robotics stack. + * + * Provides a browser-based Three.js + Rapier simulator with LCM transport, + * sensor publishing (RGB, depth, LiDAR, odometry), and an eval harness for + * automated testing of navigation and perception pipelines. + * + * ## Install + * + * ```sh + * deno install -gAf --unstable-net jsr:@antim/dimsim + * ``` + * + * ## Setup + * + * Download core assets (~22 MB) and install a scene: + * + * ```sh + * dimsim setup + * dimsim scene install apt + * ``` + * + * ## Run + * + * Start the dev server and open the URL it prints: + * + * ```sh + * dimsim dev --scene apt + * ``` + * + * Run headless evals in CI: + * + * ```sh + * dimsim eval --headless --env apt --workflow reach-vase + * ``` + * + * ## Programmatic API + * + * ```ts + * import { startBridgeServer } from "@antim/dimsim"; + * + * startBridgeServer({ port: 8090, distDir: "./dist", scene: "apt" }); + * ``` + */ + +/** Start the WebSocket bridge server that relays LCM packets between the browser and external agents. */ +export { startBridgeServer } from "./bridge/server.ts"; + +/** Launch a single headless Chromium page pointed at the sim. */ +export { launchHeadless } from "./headless/launcher.ts"; + +/** Launch multiple headless pages for parallel eval workflows. */ +export { launchMultiPage } from "./headless/launcher.ts"; + +/** Run eval workflows sequentially against a connected browser. */ +export { runEvals } from "./eval/runner.ts"; + +/** Run eval workflows distributed across multiple browser pages. */ +export { runEvalsMultiPage } from "./eval/runner.ts"; + +/** Collect workflow definitions from the manifest, optionally filtered by env/workflow name. */ +export { collectWorkflows } from "./eval/runner.ts"; + +/** Convert eval results to JUnit XML format for CI reporting. */ +export { toJunitXml } from "./eval/runner.ts"; + +/** Download and extract core DimSim assets to ~/.dimsim/. */ +export { setup } from "./setup.ts"; + +/** Download and install a scene by name from the registry. */ +export { sceneInstall } from "./setup.ts"; + +/** List installed and available scenes. */ +export { sceneList } from "./setup.ts"; + +/** Remove a locally installed scene. */ +export { sceneRemove } from "./setup.ts"; + +/** Get the DimSim home directory path (~/.dimsim or DIMSIM_HOME). */ +export { getDimsimHome } from "./setup.ts"; + +/** Get the path to the dist directory containing built frontend assets. */ +export { getDistDir } from "./setup.ts"; diff --git a/misc/DimSim/dimos-cli/run-eval.ts b/misc/DimSim/dimos-cli/run-eval.ts new file mode 100644 index 0000000000..9e44621203 --- /dev/null +++ b/misc/DimSim/dimos-cli/run-eval.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env -S deno run --allow-all --unstable-net +/** + * Run eval against an already-running bridge server. + * Usage: deno run --allow-all --unstable-net dimos-cli/run-eval.ts [--workflow reach-vase] [--port 8090] + */ +import { resolve, dirname, fromFileUrl } from "@std/path"; +import { runEvals } from "./eval/runner.ts"; + +const CLI_DIR = dirname(fromFileUrl(import.meta.url)); +const EVALS_DIR = resolve(CLI_DIR, "../evals"); + +const args = Deno.args; +let port = 8090; +let workflow: string | undefined; +let env: string | undefined; + +for (let i = 0; i < args.length; i++) { + if (args[i] === "--port" && args[i + 1]) port = parseInt(args[++i]); + if (args[i] === "--workflow" && args[i + 1]) workflow = args[++i]; + if (args[i] === "--env" && args[i + 1]) env = args[++i]; +} + +const wsUrl = `ws://localhost:${port}`; +const manifestPath = resolve(EVALS_DIR, "manifest.json"); + +console.log(`[eval] Connecting to bridge at ${wsUrl}`); +console.log(`[eval] Workflow: ${workflow || "all"}, Env: ${env || "all"}`); + +const results = await runEvals({ + wsUrl, + manifestPath, + filterEnv: env, + filterWorkflow: workflow, + outputFormat: "json", +}); + +const passed = results.filter((r) => r.pass).length; +const failed = results.length - passed; +console.log(`\n[eval] Done: ${passed} passed, ${failed} failed`); +Deno.exit(failed > 0 ? 1 : 0); diff --git a/misc/DimSim/dimos-cli/setup.ts b/misc/DimSim/dimos-cli/setup.ts new file mode 100644 index 0000000000..8bd2665204 --- /dev/null +++ b/misc/DimSim/dimos-cli/setup.ts @@ -0,0 +1,357 @@ +/** + * DimSim Setup — Downloads core assets and scenes from GitHub Releases. + * + * Local data stored at ~/.dimsim/ (override with DIMSIM_HOME env var). + * + * ~/.dimsim/ + * ├── dist/ (core frontend: index.html, assets/, agent-model/) + * │ └── sims/ (downloaded scene JSON files) + * │ └── apt.json + * └── evals/ (eval workflows) + */ + +const GITHUB_REPO = "Antim-Labs/DimSim"; +const RELEASES_API = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`; + +export function getDimsimHome(): string { + return ( + Deno.env.get("DIMSIM_HOME") || + `${Deno.env.get("HOME")}/.dimsim` + ); +} + +export function getDistDir(): string { + return `${getDimsimHome()}/dist`; +} + +// ── Registry ──────────────────────────────────────────────────────────── + +interface SceneEntry { + url: string; + description: string; + size: number; +} + +interface Registry { + version: string; + coreUrl: string; + evalsUrl?: string; + scenes: Record; +} + +async function fetchRegistry(localPath?: string): Promise { + if (localPath) { + return JSON.parse(await Deno.readTextFile(localPath)); + } + + // Fetch latest release from GitHub API, find registry.json asset + const resp = await fetch(RELEASES_API, { + headers: { Accept: "application/vnd.github.v3+json" }, + }); + if (!resp.ok) throw new Error(`Failed to fetch latest release: ${resp.status}`); + const release = await resp.json(); + + const asset = release.assets?.find( + (a: { name: string }) => a.name === "registry.json", + ); + if (!asset) { + throw new Error("registry.json not found in latest GitHub release"); + } + + const regResp = await fetch(asset.browser_download_url); + if (!regResp.ok) throw new Error(`Failed to download registry: ${regResp.status}`); + return regResp.json(); +} + +// ── Download with progress ────────────────────────────────────────────── + +async function download(url: string, dest: string): Promise { + console.log(` Downloading ${url}`); + const resp = await fetch(url); + if (!resp.ok) throw new Error(`Download failed: ${resp.status} ${url}`); + + const total = parseInt(resp.headers.get("content-length") || "0"); + const reader = resp.body!.getReader(); + const chunks: Uint8Array[] = []; + let received = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + received += value.length; + if (total > 0) { + const pct = ((received / total) * 100).toFixed(0); + const mb = (received / 1e6).toFixed(1); + Deno.stderr.writeSync( + new TextEncoder().encode(`\r ${mb} MB / ${(total / 1e6).toFixed(1)} MB (${pct}%)`) + ); + } + } + Deno.stderr.writeSync(new TextEncoder().encode("\n")); + + // Concatenate chunks into a single Uint8Array + const totalLength = chunks.reduce((sum, c) => sum + c.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + await Deno.writeFile(dest, result); +} + +// ── Extract tar.gz ────────────────────────────────────────────────────── + +async function extractTarGz(archive: string, destDir: string): Promise { + await Deno.mkdir(destDir, { recursive: true }); + const proc = new Deno.Command("tar", { + args: ["-xzf", archive, "-C", destDir], + stdout: "inherit", + stderr: "inherit", + }).spawn(); + const status = await proc.status; + if (!status.success) throw new Error(`tar extract failed (exit ${status.code})`); +} + +/** Extract a single gzipped file (not a tar, just gzip). */ +async function extractGz(archive: string, destFile: string): Promise { + const parentDir = destFile.substring(0, destFile.lastIndexOf("/")); + await Deno.mkdir(parentDir, { recursive: true }); + const proc = new Deno.Command("sh", { + args: ["-c", `gunzip -c "${archive}" > "${destFile}"`], + stdout: "inherit", + stderr: "inherit", + }).spawn(); + const status = await proc.status; + if (!status.success) throw new Error(`gunzip failed (exit ${status.code})`); +} + +// ── Version tracking ──────────────────────────────────────────────────── + +interface VersionInfo { + core?: string; + scenes?: Record; +} + +function versionPath(): string { + return `${getDimsimHome()}/version.json`; +} + +async function readVersionInfo(): Promise { + try { + return JSON.parse(await Deno.readTextFile(versionPath())); + } catch { + return {}; + } +} + +async function writeVersionInfo(info: VersionInfo): Promise { + await Deno.writeTextFile(versionPath(), JSON.stringify(info, null, 2)); +} + +// ── Public API ────────────────────────────────────────────────────────── + +export async function setup(localArchive?: string): Promise { + const home = getDimsimHome(); + const distDir = getDistDir(); + + console.log(`[dimsim] Setting up in ${home}`); + await Deno.mkdir(home, { recursive: true }); + + let registry: Registry | null = null; + + if (localArchive) { + console.log(`[dimsim] Extracting core from local archive: ${localArchive}`); + await extractTarGz(localArchive, distDir); + } else { + registry = await fetchRegistry(); + const local = await readVersionInfo(); + + if (local.core === registry.version) { + console.log(`[dimsim] Core already up-to-date (v${registry.version})`); + } else { + if (local.core) { + console.log(`[dimsim] Updating core: v${local.core} → v${registry.version}`); + } + const tmpFile = `${home}/core-download.tar.gz`; + await download(registry.coreUrl, tmpFile); + console.log(`[dimsim] Extracting core assets...`); + await extractTarGz(tmpFile, distDir); + await Deno.remove(tmpFile); + + // Write updated version + local.core = registry.version; + await writeVersionInfo(local); + } + } + + // Ensure sims directory exists + await Deno.mkdir(`${distDir}/sims`, { recursive: true }); + + // Create empty manifest if none exists + const manifestPath = `${distDir}/sims/manifest.json`; + try { + await Deno.stat(manifestPath); + } catch { + await Deno.writeTextFile(manifestPath, JSON.stringify([], null, 2)); + } + + // Install evals to ~/.dimsim/evals/ + if (registry?.evalsUrl) { + const evalsDir = `${home}/evals`; + const evalsVerFile = `${home}/evals-version`; + let installedEvalsVer: string | null = null; + try { + installedEvalsVer = (await Deno.readTextFile(evalsVerFile)).trim(); + } catch { /* not installed */ } + + if (installedEvalsVer === registry.version) { + console.log(`[dimsim] Evals already up-to-date (v${registry.version})`); + } else { + const tmpFile = `${home}/evals-download.tar.gz`; + console.log(`[dimsim] Downloading evals...`); + await download(registry.evalsUrl, tmpFile); + console.log(`[dimsim] Extracting evals...`); + await extractTarGz(tmpFile, evalsDir); + await Deno.remove(tmpFile); + await Deno.writeTextFile(evalsVerFile, registry.version); + } + } + + console.log(`[dimsim] Core setup complete.`); + console.log(`[dimsim] Install a scene: dimsim scene install apt`); +} + +export async function sceneInstall( + name: string, + localArchive?: string, +): Promise { + const home = getDimsimHome(); + const distDir = getDistDir(); + const simsDir = `${distDir}/sims`; + const destFile = `${simsDir}/${name}.json`; + + // Check core is set up + try { + await Deno.stat(distDir); + } catch { + console.error(`[dimsim] Core not installed. Run 'dimsim setup' first.`); + Deno.exit(1); + } + + await Deno.mkdir(simsDir, { recursive: true }); + + if (localArchive) { + console.log(`[dimsim] Installing scene '${name}' from local: ${localArchive}`); + if (localArchive.endsWith(".json")) { + await Deno.copyFile(localArchive, destFile); + } else { + await extractGz(localArchive, destFile); + } + } else { + const registry = await fetchRegistry(); + const entry = registry.scenes[name]; + if (!entry) { + console.error(`[dimsim] Scene '${name}' not found. Available:`); + for (const [k, v] of Object.entries(registry.scenes)) { + console.error(` ${k} — ${v.description}`); + } + Deno.exit(1); + } + + const local = await readVersionInfo(); + const localSceneVer = local.scenes?.[name]; + + if (localSceneVer === registry.version) { + console.log(`[dimsim] Scene '${name}' already up-to-date (v${registry.version})`); + return; + } + + if (localSceneVer) { + console.log(`[dimsim] Updating scene '${name}': v${localSceneVer} → v${registry.version}`); + } + const tmpFile = `${home}/${name}-download.gz`; + console.log(`[dimsim] Downloading scene '${name}' (${(entry.size / 1e6).toFixed(1)} MB)...`); + await download(entry.url, tmpFile); + console.log(`[dimsim] Extracting...`); + await extractGz(tmpFile, destFile); + await Deno.remove(tmpFile); + + // Write updated version + if (!local.scenes) local.scenes = {}; + local.scenes[name] = registry.version; + await writeVersionInfo(local); + } + + // Update local manifest + const manifestPath = `${simsDir}/manifest.json`; + let manifest: string[] = []; + try { + manifest = JSON.parse(await Deno.readTextFile(manifestPath)); + } catch { /* empty */ } + if (!manifest.includes(name)) { + manifest.push(name); + await Deno.writeTextFile(manifestPath, JSON.stringify(manifest, null, 2)); + } + + console.log(`[dimsim] Scene '${name}' installed.`); +} + +export async function sceneList(): Promise { + const simsDir = `${getDistDir()}/sims`; + + // Local scenes + const installed: string[] = []; + try { + for await (const entry of Deno.readDir(simsDir)) { + if (entry.name.endsWith(".json") && entry.name !== "manifest.json") { + installed.push(entry.name.replace(".json", "")); + } + } + } catch { /* no sims dir */ } + + // Remote scenes + let registry: Registry | null = null; + try { + registry = await fetchRegistry(); + } catch { + console.log("[dimsim] Could not fetch remote registry."); + } + + console.log("\nInstalled scenes:"); + if (installed.length === 0) { + console.log(" (none)"); + } else { + for (const s of installed) console.log(` * ${s}`); + } + + if (registry) { + console.log("\nAvailable scenes:"); + for (const [name, entry] of Object.entries(registry.scenes)) { + const status = installed.includes(name) ? " (installed)" : ""; + console.log(` ${name} — ${entry.description} (${(entry.size / 1e6).toFixed(1)} MB)${status}`); + } + } + console.log(); +} + +export async function sceneRemove(name: string): Promise { + const simsDir = `${getDistDir()}/sims`; + const scenePath = `${simsDir}/${name}.json`; + try { + await Deno.remove(scenePath); + console.log(`[dimsim] Scene '${name}' removed.`); + } catch { + console.error(`[dimsim] Scene '${name}' not found locally.`); + Deno.exit(1); + } + + // Update manifest + const manifestPath = `${simsDir}/manifest.json`; + try { + const manifest: string[] = JSON.parse(await Deno.readTextFile(manifestPath)); + const filtered = manifest.filter((s) => s !== name); + await Deno.writeTextFile(manifestPath, JSON.stringify(filtered, null, 2)); + } catch { /* ok */ } +} diff --git a/misc/DimSim/dimos-cli/test/diagnose_costmap.py b/misc/DimSim/dimos-cli/test/diagnose_costmap.py new file mode 100644 index 0000000000..5323a355af --- /dev/null +++ b/misc/DimSim/dimos-cli/test/diagnose_costmap.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Diagnostic: subscribe to /global_costmap and /odom via LCM, print costmap stats. + +Run with the dimos venv while the agent + bridge are running: + cd DimSim + ../dimos/.venv/bin/python dimos-cli/test/diagnose_costmap.py + +Prints: cell counts (FREE/OCCUPIED/UNKNOWN), robot grid position, +and whether frontier-eligible cells exist. +""" + +import sys +import time + +import numpy as np + +try: + import lcm as lcm_lib +except ImportError: + print("ERROR: 'lcm' Python package not found. Run with dimos venv.") + sys.exit(1) + +from dimos_lcm.geometry_msgs.PoseStamped import PoseStamped as LCMPoseStamped +from dimos_lcm.nav_msgs.OccupancyGrid import OccupancyGrid as LCMOccupancyGrid + + +class CostmapDiagnostic: + def __init__(self): + self.lc = lcm_lib.LCM("udpm://239.255.76.67:7667?ttl=1") + self.latest_costmap = None + self.latest_odom = None + self.costmap_count = 0 + self.odom_count = 0 + + def _on_costmap(self, channel, data): + try: + msg = LCMOccupancyGrid.lcm_decode(data) + self.latest_costmap = msg + self.costmap_count += 1 + except Exception as e: + print(f"Costmap decode error: {e}") + + def _on_odom(self, channel, data): + try: + msg = LCMPoseStamped.lcm_decode(data) + self.latest_odom = msg + self.odom_count += 1 + except Exception as e: + print(f"Odom decode error: {e}") + + def analyze(self): + if self.latest_costmap is None: + print("No costmap received yet.") + return + + msg = self.latest_costmap + w = msg.info.width + h = msg.info.height + res = msg.info.resolution + ox = msg.info.origin.position.x + oy = msg.info.origin.position.y + + grid = np.array(msg.data, dtype=np.int8).reshape(h, w) + + free_count = int(np.sum(grid == 0)) + occupied_count = int(np.sum(grid == 100)) + unknown_count = int(np.sum(grid == -1)) + other_count = int(np.sum((grid != 0) & (grid != 100) & (grid != -1))) + total = w * h + + print(f"\n{'=' * 60}") + print(f"COSTMAP #{self.costmap_count} ({w}x{h} cells, {res:.3f} m/cell)") + print(f"Origin: ({ox:.2f}, {oy:.2f})") + print(f"World extent: X=[{ox:.2f}, {ox + w * res:.2f}] Y=[{oy:.2f}, {oy + h * res:.2f}]") + print(f" FREE (0): {free_count:>8} ({100 * free_count / total:.1f}%)") + print(f" OCCUPIED (100): {occupied_count:>8} ({100 * occupied_count / total:.1f}%)") + print(f" UNKNOWN (-1): {unknown_count:>8} ({100 * unknown_count / total:.1f}%)") + print(f" OTHER (1-99): {other_count:>8} ({100 * other_count / total:.1f}%)") + + if other_count > 0: + mask = (grid != 0) & (grid != 100) & (grid != -1) + other_vals = grid[mask] + unique, counts = np.unique(other_vals, return_counts=True) + print(" Other cost distribution (top 10):") + for v, c in sorted(zip(unique, counts, strict=False), key=lambda x: -x[1])[:10]: + print(f" cost={v:>4}: {c} cells") + + # Check robot position + if self.latest_odom is not None: + rx = self.latest_odom.pose.position.x + ry = self.latest_odom.pose.position.y + rz = self.latest_odom.pose.position.z + gx = int((rx - ox) / res) + gy = int((ry - oy) / res) + print(f"\nRobot world pos: ({rx:.2f}, {ry:.2f}, {rz:.2f})") + print(f"Robot grid cell: ({gx}, {gy})") + if 0 <= gx < w and 0 <= gy < h: + print(f"Robot cell cost: {grid[gy, gx]}") + r = 5 + region = grid[max(0, gy - r) : gy + r + 1, max(0, gx - r) : gx + r + 1] + rfree = int(np.sum(region == 0)) + rocc = int(np.sum(region == 100)) + runk = int(np.sum(region == -1)) + roth = int(np.sum((region != 0) & (region != 100) & (region != -1))) + print(f"11x11 neighborhood: FREE={rfree} OCC={rocc} UNK={runk} OTHER={roth}") + else: + print("WARNING: Robot is OUTSIDE costmap bounds!") + else: + print(f"\nNo odom received (count={self.odom_count})") + + # Frontier analysis + unk_mask = grid == -1 + free_mask = grid == 0 + occ_mask = grid >= 100 + + from scipy import ndimage + + kernel = np.ones((3, 3)) + free_dilated = ndimage.binary_dilation(free_mask, structure=kernel) + occ_dilated = ndimage.binary_dilation(occ_mask, structure=kernel) + + candidates = unk_mask & free_dilated + unknown_near_free = int(np.sum(candidates)) + frontier_eligible = candidates & ~occ_dilated + frontier_count = int(np.sum(frontier_eligible)) + frontier_blocked = unknown_near_free - frontier_count + + print("\nFRONTIER ANALYSIS (pre-inflation):") + print(f" Unknown cells adjacent to free: {unknown_near_free}") + print(f" ...blocked by adjacent occupied: {frontier_blocked}") + print(f" VALID FRONTIER CELLS: {frontier_count}") + + # Simulate inflation effect + if frontier_count > 0: + inflate_radius = 0.25 # meters, default in frontier explorer + cell_radius = int(np.ceil(inflate_radius / res)) + y, x = np.ogrid[-cell_radius : cell_radius + 1, -cell_radius : cell_radius + 1] + inflate_kernel = (x**2 + y**2 <= cell_radius**2).astype(np.uint8) + inflated_occ = ndimage.binary_dilation(occ_mask, structure=inflate_kernel) + inflated_occ_dilated = ndimage.binary_dilation(inflated_occ, structure=kernel) + frontier_after_inflate = candidates & ~inflated_occ_dilated + print("\n After 0.25m inflation:") + print( + f" VALID FRONTIER CELLS: {int(np.sum(frontier_after_inflate))}" + ) + + if frontier_count == 0 and unknown_near_free > 0: + print(f"\n *** DIAGNOSIS: ALL {unknown_near_free} unknown-near-free cells") + print(" are also adjacent to occupied cells. Obstacles border every") + print(" free/unknown boundary. The height_cost algorithm may produce") + print(" high-gradient costs at edges, or the LiDAR sees obstacles") + print(" exactly at the boundary of observed space.") + elif frontier_count == 0 and free_count == 0: + print("\n *** DIAGNOSIS: No FREE (cost=0) cells at all!") + print(" The height_cost algorithm is not seeing flat ground.") + print(" Check if LiDAR produces ground-hitting points (Z<0.1 in robotics frame).") + elif frontier_count == 0 and unknown_near_free == 0 and free_count > 0: + print("\n *** DIAGNOSIS: FREE cells exist but no UNKNOWN cells border them.") + print(" The free space is fully enclosed by occupied/other-cost cells.") + + def run(self): + self.lc.subscribe("/global_costmap#nav_msgs.OccupancyGrid", self._on_costmap) + self.lc.subscribe("/odom#geometry_msgs.PoseStamped", self._on_odom) + + print("Listening on LCM for /global_costmap and /odom...") + print("Start the dimos agent + bridge in other terminals. Ctrl+C to stop.\n") + + try: + last_print = 0 + while True: + self.lc.handle_timeout(500) + now = time.time() + if now - last_print > 3.0: + print(f"\n--- msgs: costmap={self.costmap_count}, odom={self.odom_count} ---") + self.analyze() + last_print = now + except KeyboardInterrupt: + print("\nDone.") + + +if __name__ == "__main__": + CostmapDiagnostic().run() diff --git a/misc/DimSim/dimos-cli/test/dimos_integration.py b/misc/DimSim/dimos-cli/test/dimos_integration.py new file mode 100755 index 0000000000..98ceb90364 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/dimos_integration.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +DimSim ↔ dimos Integration Test (UDP Multicast) + +Validates end-to-end connectivity between DimSim (browser sim) and dimos +(Python robotics stack) via LCM UDP multicast through the bridge server. + +Data flow: + Python ──UDP multicast──▶ Bridge Server ──WebSocket──▶ Browser (DimSim) + Python ◀──UDP multicast── Bridge Server ◀──WebSocket── Browser (DimSim) + +This script: + 1. Joins the LCM multicast group (239.255.76.67:7667) + 2. Publishes /cmd_vel Twist commands as LCM packets via UDP multicast → agent moves + 3. Listens for /odom, /camera/image, /camera/depth, /lidar/points on multicast + 4. Reports what it receives; SUCCESS when all 4 channels are live + +Prerequisites: + 1. Start DimSim bridge: + ~/.deno/bin/deno run --allow-all --unstable-net dimos-cli/cli.ts dev + 2. Open http://localhost:8090 in Chrome (scene must load) + 3. Run this script from the dimos venv: + /path/to/dimos/.venv/bin/python dimos-cli/test/dimos_integration.py + +Options: + --timeout N Timeout in seconds (default: 30) + --rate N cmd_vel publish rate in Hz (default: 10) +""" + +import argparse +import socket +import struct +import sys +import threading +import time + +# dimos message types for encoding cmd_vel +from dimos.msgs.geometry_msgs import Twist, Vector3 + +# -- LCM constants ------------------------------------------------------------ +LCM_MAGIC = 0x4C433032 # "LC02" in ASCII / big-endian +MCAST_GRP = "239.255.76.67" +MCAST_PORT = 7667 +_seq = 0 + +# -- LCM packet codec (matches @dimos/msgs encodePacket / decodePacket) -------- + + +def encode_lcm_packet(channel: str, payload: bytes) -> bytes: + """Encode an LCM binary packet (same format as @dimos/msgs encodePacket).""" + global _seq + ch_bytes = channel.encode("utf-8") + buf = struct.pack(">II", LCM_MAGIC, _seq) + ch_bytes + b"\x00" + payload + _seq += 1 + return buf + + +def decode_lcm_packet(data: bytes) -> tuple[str, bytes]: + """Decode an LCM packet → (channel, payload). Raises ValueError on bad packet.""" + if len(data) < 9: + raise ValueError("Packet too short") + magic = struct.unpack_from(">I", data, 0)[0] + if magic != LCM_MAGIC: + raise ValueError(f"Bad magic: 0x{magic:08x}") + null_pos = data.index(0, 8) + channel = data[8:null_pos].decode("utf-8") + payload = data[null_pos + 1 :] + return channel, payload + + +# -- Channel names (must match DimSim's dimosBridge.ts) ------------------------ +CH_CMD_VEL = "/cmd_vel#geometry_msgs.Twist" +CH_ODOM = "/odom#geometry_msgs.PoseStamped" +CH_IMAGE = "/camera/image#sensor_msgs.Image" +CH_DEPTH = "/camera/depth#sensor_msgs.Image" +CH_LIDAR = "/lidar/points#sensor_msgs.PointCloud2" + + +def create_mcast_recv_socket() -> socket.socket: + """Create a UDP socket joined to the LCM multicast group for receiving.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except AttributeError: + pass + sock.bind(("", MCAST_PORT)) + mreq = struct.pack("4s4s", socket.inet_aton(MCAST_GRP), socket.inet_aton("0.0.0.0")) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0) + sock.settimeout(1.0) + return sock + + +def create_mcast_send_socket() -> socket.socket: + """Create a UDP socket for sending to the LCM multicast group.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0) + return sock + + +def main(): + parser = argparse.ArgumentParser(description="DimSim ↔ dimos integration test (UDP multicast)") + parser.add_argument("--timeout", type=int, default=30, help="Timeout in seconds") + parser.add_argument("--rate", type=int, default=10, help="cmd_vel publish rate (Hz)") + args = parser.parse_args() + + received = {"odom": 0, "image": 0, "depth": 0, "lidar": 0} + tick = 0 + success = False + running = True + + recv_sock = create_mcast_recv_socket() + send_sock = create_mcast_send_socket() + + print(f"[integration] LCM multicast {MCAST_GRP}:{MCAST_PORT}") + print(f"[integration] Publishing /cmd_vel at {args.rate} Hz") + print("[integration] Listening for sensor data on multicast") + print(f"[integration] Timeout: {args.timeout}s\n") + + # -- Receive thread -------------------------------------------------------- + def recv_loop(): + while running: + try: + data, addr = recv_sock.recvfrom(65536) + except TimeoutError: + continue + except OSError: + break + + try: + channel, payload = decode_lcm_packet(data) + except (ValueError, IndexError): + continue + + if "/odom" in channel: + received["odom"] += 1 + if received["odom"] <= 3 or received["odom"] % 10 == 0: + print(f"[integration] Got odom #{received['odom']} ({len(payload)}B)") + elif "/camera/image" in channel: + received["image"] += 1 + if received["image"] <= 3 or received["image"] % 10 == 0: + print(f"[integration] Got RGB #{received['image']} ({len(payload)}B)") + elif "/camera/depth" in channel: + received["depth"] += 1 + if received["depth"] <= 3 or received["depth"] % 10 == 0: + print(f"[integration] Got depth #{received['depth']} ({len(payload)}B)") + elif "/lidar/points" in channel: + received["lidar"] += 1 + if received["lidar"] <= 3 or received["lidar"] % 10 == 0: + print(f"[integration] Got LiDAR #{received['lidar']} ({len(payload)}B)") + + recv_thread = threading.Thread(target=recv_loop, daemon=True) + recv_thread.start() + + # -- Send loop ------------------------------------------------------------- + interval = 1.0 / args.rate + start_time = time.time() + + try: + while time.time() - start_time < args.timeout: + # Build Twist — Three.js identity: z=forward, y=yaw + twist = Twist( + linear=Vector3(0, 0, 0.5), + angular=Vector3(0, 0.3, 0), + ) + payload = twist.lcm_encode() + packet = encode_lcm_packet(CH_CMD_VEL, payload) + send_sock.sendto(packet, (MCAST_GRP, MCAST_PORT)) + tick += 1 + + if tick <= 3 or tick % 20 == 0: + print(f"[integration] Sent cmd_vel #{tick}") + + # Status check every 5s + elapsed = time.time() - start_time + if tick > 1 and (tick % (args.rate * 5) == 0): + print( + f"\n[integration] STATUS ({elapsed:.0f}s): " + f"cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}" + ) + + if all(v > 0 for v in received.values()): + success = True + print("\n========================================") + print(" SUCCESS: All channels working!") + print(" DimSim ↔ dimos LCM multicast verified.") + print("========================================\n") + break + + if received["odom"] == 0 and elapsed > 10: + print("[integration] No sensor data on multicast. Check:") + print(" 1. Bridge running with vendored @dimos/lcm (joinMulticastV4)") + print(" 2. Browser open at localhost:8090 with scene loaded") + print() + + time.sleep(interval) + + if not success: + print(f"\n[integration] TIMEOUT after {args.timeout}s") + print( + f"[integration] Final: cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}" + ) + + except KeyboardInterrupt: + print("\n[integration] Interrupted by user") + + finally: + running = False + # Send zero velocity (safety stop) + try: + stop_twist = Twist() + stop_pkt = encode_lcm_packet(CH_CMD_VEL, stop_twist.lcm_encode()) + send_sock.sendto(stop_pkt, (MCAST_GRP, MCAST_PORT)) + except Exception: + pass + + recv_sock.close() + send_sock.close() + print("[integration] Done.") + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/misc/DimSim/dimos-cli/test/lcm_cross_test.py b/misc/DimSim/dimos-cli/test/lcm_cross_test.py new file mode 100644 index 0000000000..d1694f0d31 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/lcm_cross_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Quick test: can Python receive LCM messages from Deno?""" + +import sys +import time + +sys.path.insert(0, "/Users/viswajitnair/Desktop/4Wall.nosync/Dimensional/dimos") +import lcm + +received = {"count": 0} + + +def handler(channel, data): + received["count"] += 1 + print(f"[py] Got message #{received['count']} on {channel} ({len(data)} bytes)") + + +lc = lcm.LCM("udpm://239.255.76.67:7667?ttl=0") +lc.subscribe(".*lcm_cross_test.*", handler) + +print("[py] Listening for LCM messages on /lcm_cross_test...") +print("[py] Waiting 15s for Deno publisher...\n") + +deadline = time.time() + 15 +while time.time() < deadline: + lc.handle_timeout(500) + if received["count"] >= 3: + print(f"\n[py] SUCCESS: received {received['count']} messages from Deno!") + sys.exit(0) + +print(f"\n[py] FAIL: only received {received['count']} messages") +sys.exit(1) diff --git a/misc/DimSim/dimos-cli/test/lcm_cross_test.ts b/misc/DimSim/dimos-cli/test/lcm_cross_test.ts new file mode 100644 index 0000000000..fde302dbc0 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/lcm_cross_test.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env -S deno run --allow-all --unstable-net +/** + * Quick test: can Deno send LCM messages that Python receives? + * Publishes raw packets on /lcm_cross_test channel. + */ +import { LCM } from "@dimos/lcm"; +import { geometry_msgs } from "@dimos/msgs"; + +const lcm = new LCM(); +await lcm.start(); + +// Also subscribe to see if we get Python's messages +lcm.subscribe("/lcm_cross_test", geometry_msgs.Twist, (msg: any) => { + console.log(`[deno] Got message back:`, msg.data?.linear); +}); + +console.log("[deno] Using patched @dimos/lcm with joinMulticastV4"); +console.log("[deno] Publishing test messages on /lcm_cross_test...\n"); + +let count = 0; +const interval = setInterval(async () => { + count++; + const twist = new geometry_msgs.Twist({ + linear: new geometry_msgs.Vector3({ x: count, y: 0, z: 0 }), + angular: new geometry_msgs.Vector3({ x: 0, y: 0, z: 0 }), + }); + await lcm.publish("/lcm_cross_test", twist); + console.log(`[deno] Sent message #${count}`); + + if (count >= 10) { + clearInterval(interval); + console.log("\n[deno] Done publishing. Waiting 2s for responses..."); + setTimeout(() => Deno.exit(0), 2000); + } +}, 500); + +await lcm.run(); diff --git a/misc/DimSim/dimos-cli/test/loopback.ts b/misc/DimSim/dimos-cli/test/loopback.ts new file mode 100644 index 0000000000..5c7392cee3 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/loopback.ts @@ -0,0 +1,120 @@ +#!/usr/bin/env -S deno run --allow-all --unstable-net + +/** + * Loopback Test — Verify bridge + DimSim sensor pipeline end-to-end. + * + * Connects to the bridge server's WebSocket (same as the browser does) and: + * 1. Publishes /cmd_vel Twist packets (simulates dimos nav stack sending velocity commands) + * 2. Listens for /odom, /camera/image, /camera/depth, /lidar/points packets back + * 3. Reports what it receives + * + * Usage: + * 1. Start bridge: deno run --allow-all --unstable-net dimos-cli/cli.ts dev + * 2. Open http://localhost:8090 in Chrome + * 3. Run this: deno run --allow-all --unstable-net dimos-cli/test/loopback.ts + */ + +import { encodePacket, decodePacket, geometry_msgs, std_msgs } from "@dimos/msgs"; + +const WS_URL = Deno.args.find((_a, i, arr) => arr[i - 1] === "--ws") || "ws://localhost:8090"; + +console.log(`[loopback] Connecting to bridge at ${WS_URL}...`); + +const ws = new WebSocket(WS_URL); +ws.binaryType = "arraybuffer"; + +const received = { odom: 0, image: 0, depth: 0, lidar: 0 }; +let tick = 0; +let cmdInterval: ReturnType | null = null; + +ws.onopen = () => { + console.log("[loopback] Connected to bridge WebSocket"); + + // Publish cmd_vel at 10Hz (agent walks forward while slowly turning) + cmdInterval = setInterval(() => { + const twist = new geometry_msgs.Twist({ + linear: new geometry_msgs.Vector3({ x: 0, y: 0, z: 0.5 }), // forward 0.5 m/s + angular: new geometry_msgs.Vector3({ x: 0, y: 0.3, z: 0 }), // turn 0.3 rad/s + }); + + const packet = encodePacket("/cmd_vel#geometry_msgs.Twist", twist); + ws.send(packet); + tick++; + + if (tick <= 3 || tick % 20 === 0) { + console.log(`[loopback] Sent cmd_vel #${tick}: linZ=0.5 angY=0.3`); + } + }, 100); +}; + +ws.onmessage = (event: MessageEvent) => { + if (!(event.data instanceof ArrayBuffer)) return; + + try { + const { channel, data } = decodePacket(new Uint8Array(event.data)); + + if (channel.includes("/odom")) { + received.odom++; + if (received.odom <= 3 || received.odom % 10 === 0) { + const pos = data.pose?.position; + const posStr = pos ? `x=${pos.x.toFixed(2)} y=${pos.y.toFixed(2)} z=${pos.z.toFixed(2)}` : "?"; + console.log(`[loopback] Got odom #${received.odom}: ${posStr}`); + } + } else if (channel.includes("/camera/image")) { + received.image++; + if (received.image <= 3 || received.image % 10 === 0) { + console.log(`[loopback] Got RGB #${received.image}`); + } + } else if (channel.includes("/camera/depth")) { + received.depth++; + if (received.depth <= 3 || received.depth % 10 === 0) { + console.log(`[loopback] Got depth #${received.depth}`); + } + } else if (channel.includes("/lidar/points")) { + received.lidar++; + if (received.lidar <= 3 || received.lidar % 10 === 0) { + console.log(`[loopback] Got LiDAR #${received.lidar}`); + } + } + } catch { + // not a valid LCM packet + } +}; + +ws.onerror = (e) => { + console.error("[loopback] WebSocket error:", e); +}; + +ws.onclose = () => { + console.log("[loopback] WebSocket closed"); + if (cmdInterval) clearInterval(cmdInterval); +}; + +// Status report every 5s +const statusInterval = setInterval(() => { + console.log(`[loopback] STATUS: cmd_sent=${tick} odom=${received.odom} rgb=${received.image} depth=${received.depth} lidar=${received.lidar}`); + if (received.odom > 0 && received.image > 0 && received.depth > 0 && received.lidar > 0) { + console.log("\n[loopback] SUCCESS: All channels working! (odom + RGB + depth + LiDAR)"); + cleanup(0); + } +}, 5000); + +// Timeout after 60s +setTimeout(() => { + console.log("\n[loopback] TIMEOUT after 60s"); + console.log(`[loopback] Final: cmd_sent=${tick} odom=${received.odom} rgb=${received.image} depth=${received.depth} lidar=${received.lidar}`); + if (received.image === 0 && received.depth === 0 && received.lidar === 0) { + console.log("[loopback] No sensor data received. Make sure:"); + console.log(" 1. Bridge server is running: deno run --allow-all dimos-cli/cli.ts dev"); + console.log(" 2. Browser is open at http://localhost:8090"); + console.log(" 3. DimSim loaded the scene (check browser console for [dimos] logs)"); + } + cleanup(1); +}, 60000); + +function cleanup(code: number) { + if (cmdInterval) clearInterval(cmdInterval); + clearInterval(statusInterval); + ws.close(); + Deno.exit(code); +} diff --git a/misc/DimSim/dimos-cli/test/rubrics_test.ts b/misc/DimSim/dimos-cli/test/rubrics_test.ts new file mode 100644 index 0000000000..9d40c90679 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/rubrics_test.ts @@ -0,0 +1,164 @@ +/** + * Unit tests for eval rubrics — pure scoring functions, no browser needed. + * + * cd DimSim && deno test dimos-cli/test/rubrics_test.ts + */ + +import { assertEquals, assertAlmostEquals } from "jsr:@std/assert"; +// Import from source — rubrics are pure TS, no DOM deps +import { + scoreObjectDistance, + scoreRadiusContains, + type SceneState, +} from "../../src/dimos/rubrics.ts"; + +// -- helpers ------------------------------------------------------------------ + +function mkScene(assets: { title: string; x: number; y: number; z: number }[], agentPos?: { x: number; y: number; z: number }): SceneState { + return { + assets: assets.map((a) => ({ + title: a.title, + transform: { x: a.x, y: a.y, z: a.z }, + })), + agentPos, + }; +} + +// -- objectDistance (existing, sanity check) ----------------------------------- + +Deno.test("objectDistance: pass when agent is close to target", () => { + const scene = mkScene([{ title: "Television", x: 5, y: 0, z: 3 }], { x: 5, y: 0, z: 3.3 }); + const result = scoreObjectDistance({ object: "agent", target: "television", thresholdM: 0.5 }, scene); + assertEquals(result.pass, true); +}); + +Deno.test("objectDistance: fail when agent is far from target", () => { + const scene = mkScene([{ title: "Television", x: 5, y: 0, z: 3 }], { x: 0, y: 0, z: 0 }); + const result = scoreObjectDistance({ object: "agent", target: "television", thresholdM: 2.0 }, scene); + assertEquals(result.pass, false); +}); + +Deno.test("objectDistance: fail when target not found", () => { + const scene = mkScene([], { x: 0, y: 0, z: 0 }); + const result = scoreObjectDistance({ object: "agent", target: "nonexistent", thresholdM: 1.0 }, scene); + assertEquals(result.pass, false); + assertEquals(result.distanceM, Infinity); +}); + +// -- radiusContains ----------------------------------------------------------- + +Deno.test("radiusContains: pass when agent is within centroid radius", () => { + // Kitchen objects form a triangle around (5, 0, 5) + const scene = mkScene( + [ + { title: "Refrigerator", x: 4, y: 0, z: 4 }, + { title: "Stove", x: 6, y: 0, z: 4 }, + { title: "Sink", x: 5, y: 0, z: 7 }, + ], + { x: 5, y: 0, z: 5 }, // agent near centroid + ); + const result = scoreRadiusContains( + { targets: ["refrigerator", "stove", "sink"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, true); + assertEquals(result.foundTargets.length, 3); + assertEquals(result.missingTargets.length, 0); + // Centroid should be (5, 0, 5) + assertAlmostEquals(result.centroid.x, 5, 0.01); + assertAlmostEquals(result.centroid.z, 5, 0.01); +}); + +Deno.test("radiusContains: fail when agent is far from centroid", () => { + const scene = mkScene( + [ + { title: "Refrigerator", x: 4, y: 0, z: 4 }, + { title: "Stove", x: 6, y: 0, z: 4 }, + { title: "Sink", x: 5, y: 0, z: 7 }, + ], + { x: 20, y: 0, z: 20 }, // agent far away + ); + const result = scoreRadiusContains( + { targets: ["refrigerator", "stove", "sink"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, false); + assertEquals(result.foundTargets.length, 3); +}); + +Deno.test("radiusContains: partial match — 2 of 3 targets found, still scores", () => { + const scene = mkScene( + [ + { title: "Refrigerator", x: 4, y: 0, z: 4 }, + { title: "Stove", x: 6, y: 0, z: 4 }, + // Sink is missing + ], + { x: 5, y: 0, z: 4 }, // agent at centroid of found targets + ); + const result = scoreRadiusContains( + { targets: ["refrigerator", "stove", "sink"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, true); + assertEquals(result.foundTargets.length, 2); + assertEquals(result.missingTargets, ["sink"]); + // Centroid should be (5, 0, 4) + assertAlmostEquals(result.centroid.x, 5, 0.01); + assertAlmostEquals(result.centroid.z, 4, 0.01); +}); + +Deno.test("radiusContains: fail when no targets found", () => { + const scene = mkScene([], { x: 0, y: 0, z: 0 }); + const result = scoreRadiusContains( + { targets: ["refrigerator", "stove"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, false); + assertEquals(result.distanceM, Infinity); + assertEquals(result.missingTargets.length, 2); +}); + +Deno.test("radiusContains: fail when agent position not available", () => { + const scene = mkScene([{ title: "Refrigerator", x: 4, y: 0, z: 4 }]); + const result = scoreRadiusContains( + { targets: ["refrigerator"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, false); +}); + +Deno.test("radiusContains: single target degrades to point distance", () => { + const scene = mkScene( + [{ title: "Bed", x: 10, y: 0, z: 10 }], + { x: 10, y: 0, z: 11 }, // 1m away + ); + const result = scoreRadiusContains( + { targets: ["bed"], radiusM: 2.0 }, + scene, + ); + assertEquals(result.pass, true); + assertAlmostEquals(result.distanceM, 1.0, 0.01); + assertEquals(result.foundTargets, ["bed"]); +}); + +Deno.test("radiusContains: exact threshold boundary", () => { + const scene = mkScene( + [ + { title: "A", x: 0, y: 0, z: 0 }, + { title: "B", x: 2, y: 0, z: 0 }, + ], + { x: 1, y: 0, z: 3 }, // centroid at (1,0,0), agent at (1,0,3) → dist = 3.0 + ); + const result = scoreRadiusContains( + { targets: ["a", "b"], radiusM: 3.0 }, + scene, + ); + assertEquals(result.pass, true); // exactly at boundary + assertAlmostEquals(result.distanceM, 3.0, 0.01); +}); + +Deno.test("radiusContains: empty targets array fails", () => { + const scene = mkScene([], { x: 0, y: 0, z: 0 }); + const result = scoreRadiusContains({ targets: [], radiusM: 3.0 }, scene); + assertEquals(result.pass, false); +}); diff --git a/misc/DimSim/dimos-cli/test/scene_editor_test.py b/misc/DimSim/dimos-cli/test/scene_editor_test.py new file mode 100644 index 0000000000..33466b426d --- /dev/null +++ b/misc/DimSim/dimos-cli/test/scene_editor_test.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Integration test for SceneEditor — script execution engine. + +Requires dimsim running headless on port 8090: + DIMSIM_HEADLESS=1 dimsim dev + +Then: + python dimos-cli/test/scene_editor_test.py +""" + +import json +import sys +import time +import uuid + +import websocket + +PORT = 8090 +WS_URL = f"ws://localhost:{PORT}?ch=control" + + +def send_exec(ws: websocket.WebSocket, code: str, timeout: float = 10) -> dict: + """Send an exec command and wait for the execResult.""" + msg_id = str(uuid.uuid4())[:8] + ws.send(json.dumps({"type": "exec", "id": msg_id, "code": code})) + ws.settimeout(timeout) + deadline = time.time() + timeout + while time.time() < deadline: + raw = ws.recv() + if isinstance(raw, bytes): + continue + msg = json.loads(raw) + if msg.get("type") == "execResult" and msg.get("id") == msg_id: + return msg + raise TimeoutError(f"No execResult for {msg_id} after {timeout}s") + + +def wait_for_scene(ws: websocket.WebSocket, timeout: float = 60) -> bool: + """Wait until sceneEditor is responding (browser has loaded).""" + deadline = time.time() + timeout + while time.time() < deadline: + try: + result = send_exec(ws, "return 'ready'", timeout=5) + if result.get("success") and result.get("result") == "ready": + return True + except Exception: + time.sleep(2) + return False + + +def test_basic_exec(ws): + """Test: basic JS evaluation returns a value.""" + print(" [1] Basic exec: return 1 + 1") + r = send_exec(ws, "return 1 + 1") + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"] == 2, f"expected 2, got {r['result']}" + print(f" PASS — result: {r['result']}") + + +def test_scene_access(ws): + """Test: can access scene.children.""" + print(" [2] Scene access: scene.children.length") + r = send_exec(ws, "return scene.children.length") + assert r["success"], f"exec failed: {r.get('error')}" + assert isinstance(r["result"], int) and r["result"] > 0, f"unexpected: {r['result']}" + print(f" PASS — scene has {r['result']} children") + + +def test_three_access(ws): + """Test: THREE namespace available, can create geometry.""" + print(" [3] THREE access: create Vector3") + r = send_exec(ws, "const v = new THREE.Vector3(1, 2, 3); return {x: v.x, y: v.y, z: v.z}") + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"] == {"x": 1, "y": 2, "z": 3}, f"unexpected: {r['result']}" + print(f" PASS — Vector3: {r['result']}") + + +def test_add_primitive(ws): + """Test: add a red box to the scene via script.""" + print(" [4] Add primitive: red box at (3, 1, 3)") + code = """ +const geo = new THREE.BoxGeometry(1, 1, 1); +const mat = new THREE.MeshStandardMaterial({color: 0xff0000}); +const mesh = new THREE.Mesh(geo, mat); +mesh.name = "test-red-box"; +mesh.position.set(3, 1, 3); +scene.add(mesh); +return {name: mesh.name, pos: {x: mesh.position.x, y: mesh.position.y, z: mesh.position.z}} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-red-box", f"unexpected: {r['result']}" + print(f" PASS — added: {r['result']}") + + # Verify it's in the scene + r2 = send_exec(ws, 'return scene.getObjectByName("test-red-box") !== null') + assert r2["success"] and r2["result"] is True, "Box not found in scene" + print(" PASS — verified in scene") + + +def test_load_gltf(ws): + """Test: load robot.glb via loadGLTF helper.""" + print(" [5] Load GLTF: /agent-model/robot.glb") + code = """ +const gltf = await loadGLTF('/agent-model/robot.glb'); +gltf.scene.name = "test-loaded-robot"; +gltf.scene.position.set(5, 1, 5); +gltf.scene.scale.set(2, 2, 2); +scene.add(gltf.scene); +return {name: gltf.scene.name, childCount: gltf.scene.children.length} +""" + r = send_exec(ws, code, timeout=15) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-loaded-robot", f"unexpected: {r['result']}" + print(f" PASS — loaded: {r['result']}") + + # Verify it's in the scene + r2 = send_exec(ws, 'return scene.getObjectByName("test-loaded-robot") !== null') + assert r2["success"] and r2["result"] is True, "Loaded robot not found in scene" + print(" PASS — verified in scene") + + +def test_error_handling(ws): + """Test: syntax/runtime errors returned gracefully.""" + print(" [6] Error handling: bad code") + r = send_exec(ws, "this is not valid javascript!!!") + assert not r["success"], "Expected failure" + assert "error" in r, "Expected error field" + print(f" PASS — error caught: {r['error'][:60]}") + + +def test_async_exec(ws): + """Test: top-level await works.""" + print(" [7] Async exec: await Promise") + code = """ +const val = await new Promise(resolve => setTimeout(() => resolve(42), 100)); +return val +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"] == 42, f"expected 42, got {r['result']}" + print(f" PASS — async result: {r['result']}") + + +def test_agent_access(ws): + """Test: can read agent position.""" + print(" [8] Agent access: getPosition") + r = send_exec(ws, "const p = agent.getPosition(); return {x: p[0], y: p[1], z: p[2]}") + assert r["success"], f"exec failed: {r.get('error')}" + assert "x" in r["result"], f"unexpected: {r['result']}" + print( + f" PASS — agent at ({r['result']['x']:.2f}, {r['result']['y']:.2f}, {r['result']['z']:.2f})" + ) + + +def test_add_light(ws): + """Test: add a point light to the scene.""" + print(" [9] Add light: PointLight at (0, 5, 0)") + code = """ +const light = new THREE.PointLight(0xffff00, 2, 50); +light.name = "test-point-light"; +light.position.set(0, 5, 0); +scene.add(light); +return {name: light.name, color: light.color.getHex(), intensity: light.intensity} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-point-light", f"unexpected: {r['result']}" + assert r["result"]["intensity"] == 2, f"unexpected intensity: {r['result']}" + print(f" PASS — added: {r['result']}") + + # Verify in scene + r2 = send_exec(ws, 'return scene.getObjectByName("test-point-light") !== null') + assert r2["success"] and r2["result"] is True, "Light not found in scene" + print(" PASS — verified in scene") + + +def test_modify_object(ws): + """Test: move an existing object (the red box from test_add_primitive).""" + print(" [10] Modify object: move test-red-box to (7, 2, 7)") + code = """ +const box = scene.getObjectByName("test-red-box"); +if (!box) return {error: "box not found"}; +box.position.set(7, 2, 7); +box.scale.set(2, 2, 2); +box.material.color.setHex(0x00ff00); +return { + name: box.name, + pos: {x: box.position.x, y: box.position.y, z: box.position.z}, + scale: {x: box.scale.x, y: box.scale.y, z: box.scale.z}, + color: box.material.color.getHex() +} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["pos"] == {"x": 7, "y": 2, "z": 7}, f"position wrong: {r['result']}" + assert r["result"]["scale"] == {"x": 2, "y": 2, "z": 2}, f"scale wrong: {r['result']}" + assert r["result"]["color"] == 0x00FF00, f"color wrong: {r['result']}" + print(f" PASS — modified: {r['result']}") + + +def test_remove_object(ws): + """Test: remove the test-red-box we added earlier.""" + print(" [11] Remove object: remove test-red-box") + remove_code = """ +const box = scene.getObjectByName("test-red-box"); +if (!box) return {error: "box not found"}; +if (box.geometry) box.geometry.dispose(); +if (box.material) box.material.dispose(); +box.name = ""; +scene.remove(box); +return {removed: "test-red-box"} +""" + r = send_exec(ws, remove_code) + assert r["success"], f"remove failed: {r.get('error')}" + print(f" PASS — removed: {r['result']}") + # Note: verification that test-red-box is gone happens in test_query_scene (test 12) + + +def test_query_scene(ws): + """Test: query scene objects by traversal.""" + print(" [12] Query scene: list named objects") + code = """ +const named = []; +scene.traverse(obj => { + if (obj.name && obj.name.startsWith("test-")) { + named.push({name: obj.name, type: obj.type}); + } +}); +return named +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + names = [o["name"] for o in r["result"]] + assert "test-point-light" in names, f"Light not found: {names}" + assert "test-loaded-robot" in names, f"Robot not found: {names}" + assert "test-red-box" not in names, f"Removed box still found: {names}" + print(f" PASS — found {len(r['result'])} test objects: {names}") + + +def test_add_sphere(ws): + """Test: add a sphere primitive (second geometry type).""" + print(" [13] Add primitive: blue sphere at (-3, 1.5, 0)") + code = """ +const geo = new THREE.SphereGeometry(0.75, 32, 32); +const mat = new THREE.MeshStandardMaterial({color: 0x0088ff, metalness: 0.3, roughness: 0.4}); +const mesh = new THREE.Mesh(geo, mat); +mesh.name = "test-blue-sphere"; +mesh.position.set(-3, 1.5, 0); +scene.add(mesh); +return {name: mesh.name, pos: {x: mesh.position.x, y: mesh.position.y, z: mesh.position.z}} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-blue-sphere", f"unexpected: {r['result']}" + print(f" PASS — added: {r['result']}") + + +def test_add_directional_light(ws): + """Test: add a directional light with shadow.""" + print(" [14] Add light: DirectionalLight") + code = """ +const dlight = new THREE.DirectionalLight(0xffffff, 1.5); +dlight.name = "test-dir-light"; +dlight.position.set(10, 10, 10); +dlight.castShadow = true; +scene.add(dlight); +return {name: dlight.name, intensity: dlight.intensity, castShadow: dlight.castShadow} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-dir-light", f"unexpected: {r['result']}" + assert r["result"]["castShadow"] is True, "Shadow not enabled" + print(f" PASS — added: {r['result']}") + + +def test_add_collider_box(ws): + """Test: add a box collider to a mesh (explicit shape).""" + print(" [15] Physics: addCollider (box)") + code = """ +const geo = new THREE.BoxGeometry(1, 1, 1); +const mat = new THREE.MeshStandardMaterial({color: 0xff8800}); +const mesh = new THREE.Mesh(geo, mat); +mesh.name = "test-physics-box"; +mesh.position.set(0, 1, 0); +scene.add(mesh); +const info = addCollider(mesh, "box"); +return info +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["shape"] == "box", f"unexpected shape: {r['result']}" + assert "uuid" in r["result"], f"no uuid: {r['result']}" + print(f" PASS — collider: {r['result']}") + + +def test_add_collider_sphere(ws): + """Test: add a sphere collider to a mesh.""" + print(" [16] Physics: addCollider (sphere)") + code = """ +const mesh = scene.getObjectByName("test-blue-sphere"); +if (!mesh) return {error: "sphere not found"}; +const info = addCollider(mesh, "sphere"); +return info +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["shape"] == "sphere", f"unexpected shape: {r['result']}" + print(f" PASS — collider: {r['result']}") + + +def test_remove_collider(ws): + """Test: remove a previously added collider.""" + print(" [17] Physics: removeCollider") + code = """ +const mesh = scene.getObjectByName("test-physics-box"); +if (!mesh) return {error: "box not found"}; +const removed = removeCollider(mesh); +return {removed} +""" + r = send_exec(ws, code) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["removed"] is True, f"collider not removed: {r['result']}" + print(f" PASS — removed: {r['result']}") + + # Verify double-remove returns false + r2 = send_exec( + ws, + """ +const mesh = scene.getObjectByName("test-physics-box"); +return {removed: removeCollider(mesh)} +""", + ) + assert r2["success"] and r2["result"]["removed"] is False, "Double remove should return false" + print(" PASS — double remove returns false") + + +def test_add_collider_trimesh(ws): + """Test: add a trimesh collider to the loaded robot.""" + print(" [18] Physics: addCollider (trimesh)") + code = """ +const robot = scene.getObjectByName("test-loaded-robot"); +if (!robot) return {error: "robot not found"}; +const info = addCollider(robot, "trimesh"); +return info +""" + r = send_exec(ws, code, timeout=15) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["shape"] == "trimesh", f"unexpected shape: {r['result']}" + print(f" PASS — collider: {r['result']}") + + +def test_add_npc(ws): + """Test: addNPC with walk animation.""" + print(" [19] NPC: addNPC (Soldier, Walk)") + code = """ +const npc = await addNPC({ + url: '/local-assets/Soldier.glb', + name: 'test-npc-soldier', + position: { x: 5, y: 0, z: 5 }, + rotation: Math.PI / 4, + scale: 1.0, + animation: 'Walk', + collider: true, +}); +return npc +""" + r = send_exec(ws, code, timeout=15) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-npc-soldier", f"unexpected: {r['result']}" + assert "Walk" in r["result"]["animations"], f"no Walk anim: {r['result']}" + assert r["result"]["activeAnimation"] == "Walk", f"wrong anim: {r['result']}" + assert r["result"]["collider"] is not None, "no collider" + print(f" PASS — NPC: {r['result']['name']}, anims: {r['result']['animations']}") + + +def test_add_npc_idle(ws): + """Test: addNPC with idle animation (by index).""" + print(" [20] NPC: addNPC (Soldier, Idle by index)") + code = """ +const npc = await addNPC({ + url: '/local-assets/Soldier.glb', + name: 'test-npc-idle', + position: { x: -5, y: 0, z: -5 }, + animation: 0, +}); +return npc +""" + r = send_exec(ws, code, timeout=15) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["name"] == "test-npc-idle", f"unexpected: {r['result']}" + assert r["result"]["activeAnimation"] == "Idle", f"wrong anim: {r['result']}" + print(f" PASS — NPC idle: {r['result']['activeAnimation']}") + + +def test_remove_npc(ws): + """Test: removeNPC removes NPC and cleans up.""" + print(" [21] NPC: removeNPC") + r = send_exec( + ws, + """ +removeNPC('test-npc-idle'); +// Check immediately in same exec — name was cleared by removeNPC +const npcs = []; +scene.traverse(obj => { if (obj.name === 'test-npc-idle') npcs.push(obj.name); }); +return { removed: true, remaining: npcs.length } +""", + ) + assert r["success"], f"exec failed: {r.get('error')}" + assert r["result"]["remaining"] == 0, f"NPC still found: {r['result']}" + print(f" PASS — removed and verified: {r['result']}") + + +def test_embodiment_config(ws): + """Test: embodiment config is accessible from scene.""" + print(" [22] Embodiment: config loaded") + r = send_exec(ws, "return window.currentEmbodiment || null") + assert r["success"], f"exec failed: {r.get('error')}" + cfg = r["result"] + if cfg is None: + print(" SKIP — not in dimos mode (embodiment only set in dimos boot)") + return + assert "radius" in cfg, f"no radius: {cfg}" + assert "halfHeight" in cfg, f"no halfHeight: {cfg}" + assert "type" in cfg, f"no type: {cfg}" + print( + f" PASS — embodiment: type={cfg['type']} radius={cfg['radius']} halfHeight={cfg['halfHeight']}" + ) + + +def main(): + print(f"Connecting to {WS_URL}...") + ws = websocket.WebSocket() + ws.connect(WS_URL) + + print("Waiting for scene to load...") + if not wait_for_scene(ws, timeout=90): + print("FAIL: scene not ready after 90s") + sys.exit(1) + print("Scene ready.\n") + + tests = [ + test_basic_exec, + test_scene_access, + test_three_access, + test_add_primitive, + test_load_gltf, + test_error_handling, + test_async_exec, + test_agent_access, + test_add_light, + test_modify_object, + test_remove_object, + test_query_scene, + test_add_sphere, + test_add_directional_light, + test_add_collider_box, + test_add_collider_sphere, + test_remove_collider, + test_add_collider_trimesh, + test_add_npc, + test_add_npc_idle, + test_remove_npc, + test_embodiment_config, + ] + + passed = 0 + failed = 0 + for test in tests: + try: + test(ws) + passed += 1 + except Exception as e: + print(f" FAIL — {e}") + failed += 1 + + ws.close() + print(f"\n{'=' * 50}") + print(f"Results: {passed} passed, {failed} failed out of {len(tests)}") + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() diff --git a/misc/DimSim/dimos-cli/test/smoke.ts b/misc/DimSim/dimos-cli/test/smoke.ts new file mode 100644 index 0000000000..636d5b7410 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/smoke.ts @@ -0,0 +1,75 @@ +#!/usr/bin/env -S deno run --allow-all --unstable-net + +/** + * Smoke Test — Verify DimSim sensor data flows over LCM. + * + * 1. Subscribes to /camera/image, /camera/depth, /lidar/points + * 2. Publishes test /odom messages + * 3. Waits for all sensor types to be received + * 4. Exits 0 on success, 1 on timeout + * + * Requires: DimSim running in dimos mode + bridge server active. + */ + +import { LCM } from "@dimos/lcm"; +import { geometry_msgs, sensor_msgs, std_msgs } from "@dimos/msgs"; + +const TIMEOUT_MS = 30000; + +const lcm = new LCM(); +await lcm.start(); + +let receivedImage = false; +let receivedDepth = false; +let receivedLidar = false; + +lcm.subscribe("/camera/image", sensor_msgs.Image, (msg: { data: { width: number; height: number; encoding: string } }) => { + console.log(`[smoke] Got RGB: ${msg.data.width}x${msg.data.height} enc=${msg.data.encoding}`); + receivedImage = true; +}); + +lcm.subscribe("/camera/depth", sensor_msgs.Image, (msg: { data: { width: number; height: number; encoding: string } }) => { + console.log(`[smoke] Got depth: ${msg.data.width}x${msg.data.height} enc=${msg.data.encoding}`); + receivedDepth = true; +}); + +lcm.subscribe("/lidar/points", sensor_msgs.PointCloud2, (msg: { data: { width: number; point_step: number } }) => { + console.log(`[smoke] Got LiDAR: ${msg.data.width} points, ${msg.data.point_step} bytes/pt`); + receivedLidar = true; +}); + +// Publish test odom at 10 Hz to drive the agent +const odomInterval = setInterval(async () => { + const pose = new geometry_msgs.PoseStamped({ + header: new std_msgs.Header({ + stamp: new std_msgs.Time({ sec: 0, nsec: 0 }), + frame_id: "map", + }), + pose: new geometry_msgs.Pose({ + position: new geometry_msgs.Point({ x: 0, y: 0.5, z: 0 }), + orientation: new geometry_msgs.Quaternion({ x: 0, y: 0, z: 0, w: 1 }), + }), + }); + await lcm.publish("/odom", pose); +}, 100); + +// Poll for results +const deadline = Date.now() + TIMEOUT_MS; +const checkInterval = setInterval(() => { + if (receivedImage && receivedDepth && receivedLidar) { + clearInterval(odomInterval); + clearInterval(checkInterval); + console.log("\n[smoke] PASS: All sensor types received"); + Deno.exit(0); + } + if (Date.now() > deadline) { + clearInterval(odomInterval); + clearInterval(checkInterval); + console.error("\n[smoke] FAIL: Timeout. Missing sensors:", { + image: receivedImage, + depth: receivedDepth, + lidar: receivedLidar, + }); + Deno.exit(1); + } +}, 500); diff --git a/misc/DimSim/dimos-cli/vendor/lcm/lcm.ts b/misc/DimSim/dimos-cli/vendor/lcm/lcm.ts new file mode 100644 index 0000000000..f4aa7b2c96 --- /dev/null +++ b/misc/DimSim/dimos-cli/vendor/lcm/lcm.ts @@ -0,0 +1,236 @@ +// LCM Main Class - Pure TypeScript Implementation (vendored from @dimos/lcm@0.2.0) + +import type { + LCMOptions, + LCMMessage, + MessageClass, + ParsedUrl, + Subscription, + SubscriptionHandler, + PacketHandler, + PacketSubscription, +} from "./types.ts"; +import { MAX_SMALL_MESSAGE, SHORT_HEADER_SIZE } from "./types.ts"; +import { parseUrl } from "./url.ts"; +import { + UdpMulticastSocket, + FragmentReassembler, + encodeSmallMessage, + encodeFragmentedMessage, + decodePacket, +} from "./transport.ts"; + +const textEncoder = new TextEncoder(); + +export class LCM { + private readonly config: ParsedUrl; + private socket: UdpMulticastSocket | null = null; + private reassembler = new FragmentReassembler(); + private subscriptions: Subscription[] = []; + private packetSubscriptions: PacketSubscription[] = []; + private sequenceNumber = 0; + private running = false; + private messageQueue: LCMMessage[] = []; + + constructor(url?: string); + constructor(options?: LCMOptions); + constructor(urlOrOptions?: string | LCMOptions) { + if (typeof urlOrOptions === "string") { + this.config = parseUrl(urlOrOptions); + } else { + this.config = parseUrl(urlOrOptions?.url); + if (urlOrOptions?.ttl !== undefined) { + this.config.ttl = urlOrOptions.ttl; + } + if (urlOrOptions?.iface !== undefined) { + this.config.iface = urlOrOptions.iface; + } + } + } + + async start(): Promise { + if (this.running) return; + this.socket = new UdpMulticastSocket(this.config); + this.running = true; + await this.socket.listen((data, _addr) => { + this.handlePacket(data); + }); + } + + stop(): void { + this.running = false; + if (this.socket) { + this.socket.close(); + this.socket = null; + } + } + + subscribeRaw(channelPattern: string, handler: SubscriptionHandler): () => void { + const pattern = this.channelToRegex(channelPattern); + const subscription: Subscription = { + channel: channelPattern, + pattern, + handler: handler as SubscriptionHandler, + }; + this.subscriptions.push(subscription); + return () => { + const idx = this.subscriptions.indexOf(subscription); + if (idx !== -1) this.subscriptions.splice(idx, 1); + }; + } + + subscribePacket(handler: PacketHandler): () => void; + subscribePacket(channelPattern: string, handler: PacketHandler): () => void; + subscribePacket(patternOrHandler: string | PacketHandler, maybeHandler?: PacketHandler): () => void { + const pattern = typeof patternOrHandler === "string" + ? this.channelToRegex(patternOrHandler) + : null; + const handler = typeof patternOrHandler === "function" + ? patternOrHandler + : maybeHandler!; + const subscription: PacketSubscription = { pattern, handler }; + this.packetSubscriptions.push(subscription); + return () => { + const idx = this.packetSubscriptions.indexOf(subscription); + if (idx !== -1) this.packetSubscriptions.splice(idx, 1); + }; + } + + subscribe(channel: string, msgClass: MessageClass, handler: SubscriptionHandler): () => void { + const typeName = (msgClass as unknown as { _NAME: string })._NAME; + const fullChannel = channel.includes("#") ? channel : `${channel}#${typeName}`; + const pattern = this.channelToRegex(fullChannel); + const subscription: Subscription = { + channel: fullChannel, + pattern, + handler: handler as SubscriptionHandler, + msgClass: msgClass as MessageClass, + }; + this.subscriptions.push(subscription); + return () => { + const idx = this.subscriptions.indexOf(subscription); + if (idx !== -1) this.subscriptions.splice(idx, 1); + }; + } + + async publishRaw(channel: string, data: Uint8Array): Promise { + if (!this.socket) throw new Error("LCM not started. Call start() first."); + const channelBytes = textEncoder.encode(channel); + const totalSize = SHORT_HEADER_SIZE + channelBytes.length + 1 + data.length; + const seq = this.sequenceNumber++; + if (totalSize <= MAX_SMALL_MESSAGE) { + const packet = encodeSmallMessage(channel, data, seq); + await this.socket.send(packet); + } else { + const fragments = encodeFragmentedMessage(channel, data, seq); + for (const fragment of fragments) { + await this.socket.send(fragment); + } + } + } + + async publish(channel: string, msg: T): Promise { + const data = msg.encode(); + const typeName = (msg.constructor as unknown as { _NAME?: string })._NAME; + const fullChannel = typeName && !channel.includes("#") + ? `${channel}#${typeName}` + : channel; + await this.publishRaw(fullChannel, data); + } + + async publishPacket(packet: Uint8Array): Promise { + if (!this.socket) throw new Error("LCM not started. Call start() first."); + await this.socket.send(packet); + } + + handle(timeoutMs: number = 0): number { + const messages = this.messageQueue.splice(0); + for (const msg of messages) { + this.dispatchMessage(msg); + } + return messages.length; + } + + async handleAsync(timeoutMs: number = 100): Promise { + const startTime = Date.now(); + while (this.messageQueue.length === 0) { + if (timeoutMs >= 0 && Date.now() - startTime >= timeoutMs) break; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + return this.handle(); + } + + async run(callback?: () => void | Promise): Promise { + while (this.running) { + await this.handleAsync(100); + if (callback) await callback(); + } + } + + private channelToRegex(pattern: string): RegExp { + const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&"); + const regexStr = "^" + escaped.replace(/\*/g, ".*") + "$"; + return new RegExp(regexStr); + } + + private handlePacket(data: Uint8Array): void { + const decoded = decodePacket(data); + if (!decoded) return; + + const channel = decoded.type === "small" ? decoded.channel : decoded.channel; + + if (channel) { + for (const sub of this.packetSubscriptions) { + if (!sub.pattern || sub.pattern.test(channel)) { + try { sub.handler(data); } catch (e) { console.error(`Error in raw packet handler:`, e); } + } + } + } + + if (decoded.type === "small") { + this.queueMessage(decoded.channel, decoded.data); + } else { + const complete = this.reassembler.processFragment(decoded); + if (complete) this.queueMessage(complete.channel, complete.data); + } + } + + private queueMessage(channel: string, data: Uint8Array): void { + const msg: LCMMessage = { + channel, + data: new Uint8Array(data), + timestamp: Date.now(), + }; + this.messageQueue.push(msg); + } + + private dispatchMessage(msg: LCMMessage): void { + for (const sub of this.subscriptions) { + if (sub.pattern.test(msg.channel)) { + try { + if (sub.msgClass) { + const decoded = sub.msgClass.decode(msg.data); + sub.handler({ channel: msg.channel, data: decoded, timestamp: msg.timestamp }); + } else { + sub.handler(msg); + } + } catch (e) { + console.error(`Error in subscription handler for ${msg.channel}:`, e); + } + } + } + } + + getConfig(): ParsedUrl { + return { ...this.config }; + } + + isRunning(): boolean { + return this.running; + } + + /** Peek at the next sequence number (for echo filtering). */ + getNextSeq(): number { + return this.sequenceNumber; + } +} diff --git a/misc/DimSim/dimos-cli/vendor/lcm/mod.ts b/misc/DimSim/dimos-cli/vendor/lcm/mod.ts new file mode 100644 index 0000000000..f1ad2a5bd1 --- /dev/null +++ b/misc/DimSim/dimos-cli/vendor/lcm/mod.ts @@ -0,0 +1,6 @@ +// LCM Pure TypeScript Implementation (vendored from @dimos/lcm@0.2.0) +// FIX: Added joinMulticastV4() in transport.ts + +export { LCM } from "./lcm.ts"; +export type { LCMOptions, LCMMessage, MessageClass, ParsedUrl, PacketHandler } from "./types.ts"; +export { parseUrl, DEFAULT_MULTICAST_GROUP, DEFAULT_PORT } from "./url.ts"; diff --git a/misc/DimSim/dimos-cli/vendor/lcm/transport.ts b/misc/DimSim/dimos-cli/vendor/lcm/transport.ts new file mode 100644 index 0000000000..b98a6bc866 --- /dev/null +++ b/misc/DimSim/dimos-cli/vendor/lcm/transport.ts @@ -0,0 +1,352 @@ +// LCM UDP Multicast Transport (vendored from @dimos/lcm@0.2.0) +// FIX: Added joinMulticastV4() call in UdpMulticastSocket.listen() + +import { + MAGIC_SHORT, + MAGIC_LONG, + MAX_SMALL_MESSAGE, + SHORT_HEADER_SIZE, + FRAGMENT_HEADER_SIZE, +} from "./types.ts"; +import type { ParsedUrl } from "./types.ts"; + +const textEncoder = new TextEncoder(); +const textDecoder = new TextDecoder(); + +/** Encode a small LCM message (fits in single UDP packet) */ +export function encodeSmallMessage( + channel: string, + data: Uint8Array, + sequenceNumber: number +): Uint8Array { + const channelBytes = textEncoder.encode(channel); + const totalSize = SHORT_HEADER_SIZE + channelBytes.length + 1 + data.length; + + if (totalSize > MAX_SMALL_MESSAGE) { + throw new Error(`Message too large for small message format: ${totalSize} > ${MAX_SMALL_MESSAGE}`); + } + + const buffer = new Uint8Array(totalSize); + const view = new DataView(buffer.buffer); + + let offset = 0; + + // Magic number (big-endian) + view.setUint32(offset, MAGIC_SHORT, false); + offset += 4; + + // Sequence number (big-endian) + view.setUint32(offset, sequenceNumber, false); + offset += 4; + + // Channel name (null-terminated) + buffer.set(channelBytes, offset); + offset += channelBytes.length; + buffer[offset] = 0; // null terminator + offset += 1; + + // Payload + buffer.set(data, offset); + + return buffer; +} + +/** Encode a fragmented LCM message (requires multiple UDP packets) */ +export function encodeFragmentedMessage( + channel: string, + data: Uint8Array, + sequenceNumber: number, + maxFragmentSize: number = 65000 +): Uint8Array[] { + const channelBytes = textEncoder.encode(channel); + const payloadSize = data.length; + + const firstFragmentPayloadSpace = maxFragmentSize - FRAGMENT_HEADER_SIZE - channelBytes.length - 1; + const subsequentFragmentPayloadSpace = maxFragmentSize - FRAGMENT_HEADER_SIZE; + + let numFragments = 1; + let remainingBytes = payloadSize - Math.min(payloadSize, firstFragmentPayloadSpace); + if (remainingBytes > 0) { + numFragments += Math.ceil(remainingBytes / subsequentFragmentPayloadSpace); + } + + const fragments: Uint8Array[] = []; + let payloadOffset = 0; + + for (let fragmentNum = 0; fragmentNum < numFragments; fragmentNum++) { + const isFirst = fragmentNum === 0; + const headerSize = FRAGMENT_HEADER_SIZE; + const channelSize = isFirst ? channelBytes.length + 1 : 0; + + const maxPayloadForThisFragment = isFirst + ? firstFragmentPayloadSpace + : subsequentFragmentPayloadSpace; + + const payloadForThisFragment = Math.min( + maxPayloadForThisFragment, + payloadSize - payloadOffset + ); + + const fragmentSize = headerSize + channelSize + payloadForThisFragment; + const fragment = new Uint8Array(fragmentSize); + const view = new DataView(fragment.buffer); + + let offset = 0; + + view.setUint32(offset, MAGIC_LONG, false); + offset += 4; + view.setUint32(offset, sequenceNumber, false); + offset += 4; + view.setUint32(offset, payloadSize, false); + offset += 4; + view.setUint32(offset, payloadOffset, false); + offset += 4; + view.setUint16(offset, fragmentNum, false); + offset += 2; + view.setUint16(offset, numFragments, false); + offset += 2; + + if (isFirst) { + fragment.set(channelBytes, offset); + offset += channelBytes.length; + fragment[offset] = 0; + offset += 1; + } + + fragment.set(data.subarray(payloadOffset, payloadOffset + payloadForThisFragment), offset); + payloadOffset += payloadForThisFragment; + fragments.push(fragment); + } + + return fragments; +} + +/** Decoded small message */ +export interface DecodedSmallMessage { + type: "small"; + channel: string; + data: Uint8Array; + sequenceNumber: number; +} + +/** Decoded fragment */ +export interface DecodedFragment { + type: "fragment"; + sequenceNumber: number; + payloadSize: number; + fragmentOffset: number; + fragmentNumber: number; + numFragments: number; + channel?: string; + data: Uint8Array; +} + +/** Decode a received UDP packet */ +export function decodePacket(packet: Uint8Array): DecodedSmallMessage | DecodedFragment | null { + if (packet.length < SHORT_HEADER_SIZE) { + return null; + } + + const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength); + const magic = view.getUint32(0, false); + + if (magic === MAGIC_SHORT) { + return decodeSmallPacket(packet, view); + } else if (magic === MAGIC_LONG) { + return decodeFragmentPacket(packet, view); + } + + return null; +} + +function decodeSmallPacket(packet: Uint8Array, view: DataView): DecodedSmallMessage | null { + const sequenceNumber = view.getUint32(4, false); + + let channelEnd = SHORT_HEADER_SIZE; + while (channelEnd < packet.length && packet[channelEnd] !== 0) { + channelEnd++; + } + + if (channelEnd >= packet.length) { + return null; + } + + const channel = textDecoder.decode(packet.subarray(SHORT_HEADER_SIZE, channelEnd)); + const data = packet.subarray(channelEnd + 1); + + return { type: "small", channel, data, sequenceNumber }; +} + +function decodeFragmentPacket(packet: Uint8Array, view: DataView): DecodedFragment | null { + if (packet.length < FRAGMENT_HEADER_SIZE) { + return null; + } + + const sequenceNumber = view.getUint32(4, false); + const payloadSize = view.getUint32(8, false); + const fragmentOffset = view.getUint32(12, false); + const fragmentNumber = view.getUint16(16, false); + const numFragments = view.getUint16(18, false); + + let offset = FRAGMENT_HEADER_SIZE; + let channel: string | undefined; + + if (fragmentNumber === 0) { + let channelEnd = offset; + while (channelEnd < packet.length && packet[channelEnd] !== 0) { + channelEnd++; + } + if (channelEnd >= packet.length) { + return null; + } + channel = textDecoder.decode(packet.subarray(offset, channelEnd)); + offset = channelEnd + 1; + } + + const data = packet.subarray(offset); + + return { type: "fragment", sequenceNumber, payloadSize, fragmentOffset, fragmentNumber, numFragments, channel, data }; +} + +/** Fragment reassembler for handling large messages */ +export class FragmentReassembler { + private pending = new Map; + buffer: Uint8Array; + lastActivity: number; + }>(); + + private timeoutMs: number; + + constructor(timeoutMs: number = 5000) { + this.timeoutMs = timeoutMs; + } + + processFragment(fragment: DecodedFragment): { channel: string; data: Uint8Array } | null { + const now = Date.now(); + this.cleanup(now); + + let entry = this.pending.get(fragment.sequenceNumber); + + if (!entry) { + if (fragment.fragmentNumber !== 0 || !fragment.channel) { + return null; + } + + entry = { + channel: fragment.channel, + payloadSize: fragment.payloadSize, + numFragments: fragment.numFragments, + receivedFragments: new Set(), + buffer: new Uint8Array(fragment.payloadSize), + lastActivity: now, + }; + this.pending.set(fragment.sequenceNumber, entry); + } + + if (fragment.fragmentOffset + fragment.data.length > entry.buffer.length) { + // Fragment doesn't fit — corrupted or mismatched packet, discard. + this.pending.delete(fragment.sequenceNumber); + return null; + } + entry.buffer.set(fragment.data, fragment.fragmentOffset); + entry.receivedFragments.add(fragment.fragmentNumber); + entry.lastActivity = now; + + if (entry.receivedFragments.size === entry.numFragments) { + this.pending.delete(fragment.sequenceNumber); + return { channel: entry.channel, data: entry.buffer }; + } + + return null; + } + + private cleanup(now: number): void { + for (const [seq, entry] of this.pending) { + if (now - entry.lastActivity > this.timeoutMs) { + this.pending.delete(seq); + } + } + } +} + +/** UDP Multicast socket wrapper for Deno */ +export class UdpMulticastSocket { + private socket: Deno.DatagramConn | null = null; + private readonly config: ParsedUrl; + private running = false; + + constructor(config: ParsedUrl) { + this.config = config; + } + + /** Start listening for multicast messages */ + async listen(onMessage: (data: Uint8Array, addr: Deno.NetAddr) => void): Promise { + // reuseAddress allows multiple processes to bind to the same multicast port + this.socket = Deno.listenDatagram({ + port: this.config.port, + transport: "udp", + hostname: "0.0.0.0", + reuseAddress: true, + }); + + // FIX: Join the multicast group and enable loopback for local IPC + const membership = await this.socket.joinMulticastV4(this.config.host, this.config.iface ?? "0.0.0.0"); + membership.setLoopback(true); + if (this.config.ttl !== undefined) { + membership.setTTL(this.config.ttl); + } + + this.running = true; + + // Read loop + (async () => { + try { + while (this.running && this.socket) { + const [data, addr] = await this.socket.receive(); + if (addr.transport === "udp") { + onMessage(data, addr); + } + } + } catch (e) { + if (this.running) { + console.error("UDP receive error:", e); + } + } + })(); + } + + /** Send a UDP packet to the multicast group */ + async send(data: Uint8Array): Promise { + if (!this.socket) { + // Create a socket for sending if we don't have one + this.socket = Deno.listenDatagram({ + port: 0, // Ephemeral port for sending + transport: "udp", + hostname: "0.0.0.0", + }); + } + + await this.socket.send(data, { + transport: "udp", + hostname: this.config.host, + port: this.config.port, + }); + } + + /** Close the socket */ + close(): void { + this.running = false; + if (this.socket) { + try { + this.socket.close(); + } catch { + // Ignore close errors + } + this.socket = null; + } + } +} diff --git a/misc/DimSim/dimos-cli/vendor/lcm/types.ts b/misc/DimSim/dimos-cli/vendor/lcm/types.ts new file mode 100644 index 0000000000..2f4e1a14b3 --- /dev/null +++ b/misc/DimSim/dimos-cli/vendor/lcm/types.ts @@ -0,0 +1,62 @@ +// LCM Type Definitions (vendored from @dimos/lcm@0.2.0) + +/** LCM message as received */ +export interface LCMMessage { + channel: string; + data: T; + timestamp: number; +} + +/** Interface for LCM message classes (generated types) */ +export interface MessageClass { + readonly _HASH: bigint; + readonly _NAME: string; + decode(data: Uint8Array): T; + new (init?: Partial): T & { encode(): Uint8Array }; +} + +/** Subscription handler function */ +export type SubscriptionHandler = (msg: LCMMessage) => void; + +/** Packet handler function (for raw UDP packets) */ +export type PacketHandler = (packet: Uint8Array) => void; + +/** LCM configuration options */ +export interface LCMOptions { + /** LCM URL (e.g., "udpm://239.255.76.67:7667?ttl=1") */ + url?: string; + /** Multicast TTL (time-to-live) */ + ttl?: number; + /** Network interface to bind to */ + iface?: string; +} + +/** Parsed LCM URL */ +export interface ParsedUrl { + scheme: string; + host: string; + port: number; + ttl: number; + iface?: string; +} + +/** Internal subscription record */ +export interface Subscription { + channel: string; + pattern: RegExp; + handler: SubscriptionHandler; + msgClass?: MessageClass; +} + +/** Internal packet subscription record */ +export interface PacketSubscription { + pattern: RegExp | null; // null = match all + handler: PacketHandler; +} + +// LCM Protocol constants +export const MAGIC_SHORT = 0x4c433032; // "LC02" +export const MAGIC_LONG = 0x4c433033; // "LC03" +export const MAX_SMALL_MESSAGE = 65535; +export const SHORT_HEADER_SIZE = 8; +export const FRAGMENT_HEADER_SIZE = 20; diff --git a/misc/DimSim/dimos-cli/vendor/lcm/url.ts b/misc/DimSim/dimos-cli/vendor/lcm/url.ts new file mode 100644 index 0000000000..ecad329e02 --- /dev/null +++ b/misc/DimSim/dimos-cli/vendor/lcm/url.ts @@ -0,0 +1,61 @@ +// LCM URL Parser (vendored from @dimos/lcm@0.2.0) + +import type { ParsedUrl } from "./types.ts"; + +export const DEFAULT_MULTICAST_GROUP = "239.255.76.67"; +export const DEFAULT_PORT = 7667; +export const DEFAULT_TTL = 0; + +/** + * Parse an LCM URL into its components. + * + * Supported formats: + * - "udpm://239.255.76.67:7667?ttl=1" + * - "udpm://239.255.76.67:7667" + * - "udpm://" (uses defaults) + * - "" (uses defaults) + */ +export function parseUrl(url?: string): ParsedUrl { + if (!url || url === "" || url === "udpm://") { + return { + scheme: "udpm", + host: DEFAULT_MULTICAST_GROUP, + port: DEFAULT_PORT, + ttl: DEFAULT_TTL, + }; + } + + const match = url.match(/^(\w+):\/\/([^:/?#]+)?(?::(\d+))?(?:\?(.*))?$/); + if (!match) { + throw new Error(`Invalid LCM URL: ${url}`); + } + + const [, scheme, host, portStr, queryStr] = match; + + if (scheme !== "udpm") { + throw new Error(`Unsupported LCM scheme: ${scheme} (only "udpm" is supported)`); + } + + const port = portStr ? parseInt(portStr, 10) : DEFAULT_PORT; + + // Parse query parameters + let ttl = DEFAULT_TTL; + let iface: string | undefined; + + if (queryStr) { + const params = new URLSearchParams(queryStr); + const ttlStr = params.get("ttl"); + if (ttlStr) { + ttl = parseInt(ttlStr, 10); + } + iface = params.get("iface") ?? undefined; + } + + return { + scheme, + host: host || DEFAULT_MULTICAST_GROUP, + port, + ttl, + iface, + }; +} diff --git a/misc/DimSim/docker/ci-test/Dockerfile b/misc/DimSim/docker/ci-test/Dockerfile new file mode 100644 index 0000000000..c0b51c7c17 --- /dev/null +++ b/misc/DimSim/docker/ci-test/Dockerfile @@ -0,0 +1,37 @@ +FROM debian:bookworm-slim + +# System deps: Playwright browser deps + multicast tools +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl unzip ca-certificates iproute2 procps \ + fonts-liberation libnss3 libatk-bridge2.0-0 \ + libdrm2 libxcomposite1 libxdamage1 libxrandr2 libgbm1 \ + libpango-1.0-0 libcairo2 libasound2 libxshmfence1 \ + libx11-xcb1 libxcb1 libxext6 libxfixes3 libxi6 \ + libxrender1 libxtst6 libglib2.0-0 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* + +# Install Deno +RUN curl -fsSL https://deno.land/install.sh | sh +ENV DENO_DIR=/root/.deno +ENV PATH="/root/.deno/bin:$PATH" + +WORKDIR /app + +# DimSim — pre-built frontend + CLI tooling +COPY DimSim/dist/ ./DimSim/dist/ +COPY DimSim/dimos-cli/ ./DimSim/dimos-cli/ +COPY DimSim/evals/ ./DimSim/evals/ + +# dimos — TS language interop controller +COPY dimos/examples/language-interop/ts/ ./dimos/examples/language-interop/ts/ + +# Entrypoint +COPY DimSim/docker/ci-test/run-test.sh ./run-test.sh +RUN chmod +x run-test.sh + +# Pre-cache Deno deps and install Playwright's Chromium +RUN cd DimSim && deno cache --unstable-net dimos-cli/cli.ts || true +RUN cd /app/dimos/examples/language-interop/ts && deno cache --unstable-net main_custom_multicast.ts || true +RUN cd DimSim && deno run -A npm:playwright install --with-deps chromium || true + +ENTRYPOINT ["./run-test.sh"] diff --git a/misc/DimSim/docker/ci-test/build-and-test.sh b/misc/DimSim/docker/ci-test/build-and-test.sh new file mode 100755 index 0000000000..68b7c39e2b --- /dev/null +++ b/misc/DimSim/docker/ci-test/build-and-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Navigate to Dimensional/ (parent of DimSim/ and dimos/) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DIMENSIONAL_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +echo "Build context: $DIMENSIONAL_DIR" +echo "Building CI test image..." + +docker build \ + -f "$SCRIPT_DIR/Dockerfile" \ + -t dimsim-ci-test \ + "$DIMENSIONAL_DIR" + +echo "" +echo "Running integration test..." +docker run --rm \ + --cap-add=NET_ADMIN \ + dimsim-ci-test diff --git a/misc/DimSim/docker/ci-test/run-test.sh b/misc/DimSim/docker/ci-test/run-test.sh new file mode 100755 index 0000000000..66ee716030 --- /dev/null +++ b/misc/DimSim/docker/ci-test/run-test.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=== DimSim + dimos Integration Test ===" + +# --- Multicast setup --- +echo "Setting up multicast routing..." +ip link set lo multicast on 2>/dev/null || true +ip route add 224.0.0.0/4 dev lo 2>/dev/null || true + +# --- Start bridge server (DimSim) --- +echo "Starting DimSim bridge server..." +cd /app/DimSim +deno run --allow-all --unstable-net \ + dimos-cli/cli.ts dev --scene apt --port 8090 --headless --render cpu \ + &> /tmp/bridge.log & +BRIDGE_PID=$! +cd /app + +# Wait for HTTP to be ready +echo "Waiting for bridge..." +for i in $(seq 1 30); do + if curl -s http://localhost:8090 > /dev/null 2>&1; then + echo "Bridge ready (${i}s)" + break + fi + if [ "$i" -eq 30 ]; then + echo "FAIL: Bridge never started" + cat /tmp/bridge.log + exit 1 + fi + sleep 1 +done + +# Wait for headless browser to load scene + start publishing +# SwiftShader CPU rendering is very slow — needs more time +echo "Waiting for scene load (30s)..." +sleep 30 + +# --- Run dimos TS controller --- +echo "Starting dimos TS controller (main_custom_multicast.ts)..." +echo "Will timeout after 60s if no data received." + +# Create sensor output dir so the controller doesn't crash +mkdir -p /app/sensor_output + +cd /app/dimos/examples/language-interop/ts +timeout 60 deno run --allow-net --allow-write --unstable-net \ + main_custom_multicast.ts &> /tmp/controller.log & +CTRL_PID=$! +cd /app + +# Monitor: wait for 3+ odom messages (poll controller output) +PASS=false +for i in $(seq 1 60); do + ODOM_COUNT="$(grep -c '\[recv\] odom' /tmp/controller.log 2>/dev/null)" || ODOM_COUNT=0 + if [ "$ODOM_COUNT" -ge 3 ]; then + PASS=true + break + fi + sleep 1 +done + +# Cleanup +kill $CTRL_PID 2>/dev/null || true +kill $BRIDGE_PID 2>/dev/null || true + +echo "" +echo "--- Controller output ---" +cat /tmp/controller.log + +if $PASS; then + echo "" + echo "=== TEST PASSED — dimos ↔ DimSim multicast integration works ===" + exit 0 +else + echo "" + echo "=== TEST FAILED — no odom data received ===" + echo "--- Bridge logs ---" + tail -50 /tmp/bridge.log + exit 1 +fi diff --git a/misc/DimSim/docker/cli-test/Dockerfile b/misc/DimSim/docker/cli-test/Dockerfile new file mode 100644 index 0000000000..0525a640d9 --- /dev/null +++ b/misc/DimSim/docker/cli-test/Dockerfile @@ -0,0 +1,23 @@ +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl unzip ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install Deno +RUN curl -fsSL https://deno.land/install.sh | sh +ENV PATH="/root/.deno/bin:$PATH" + +WORKDIR /app + +# Install CLI from JSR (the real install path) +RUN deno install -gAf --name dimsim --allow-all --unstable-net jsr:@antim/dimsim + +# Copy release tarballs for local setup test +COPY dimsim-core-v0.1.0.tar.gz ./ +COPY scene-apt-v0.1.0.tar.gz ./ + +COPY docker/cli-test/test.sh ./test.sh +RUN chmod +x test.sh + +ENTRYPOINT ["./test.sh"] diff --git a/misc/DimSim/docker/cli-test/test.sh b/misc/DimSim/docker/cli-test/test.sh new file mode 100755 index 0000000000..5736049857 --- /dev/null +++ b/misc/DimSim/docker/cli-test/test.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=== DimSim CLI Install Test ===" +echo "" + +# 1. Verify CLI is installed +echo "--- Step 1: Verify CLI installed ---" +which dimsim +dimsim help 2>&1 | head -5 +echo "" + +# 2. Setup core assets +echo "--- Step 2: dimsim setup ---" +dimsim setup --local /app/dimsim-core-v0.1.0.tar.gz +echo "" + +# 3. Verify core installed +echo "--- Step 3: Verify core ---" +ls -la ~/.dimsim/dist/ +echo "" + +# 4. Install apt scene +echo "--- Step 4: dimsim scene install apt ---" +dimsim scene install apt --local /app/scene-apt-v0.1.0.tar.gz +echo "" + +# 5. List scenes +echo "--- Step 5: dimsim scene list ---" +dimsim scene list +echo "" + +# 6. Verify scene installed +echo "--- Step 6: Verify scene files ---" +ls -lh ~/.dimsim/dist/sims/ +echo "" + +# 7. Start dev server briefly and verify it responds +echo "--- Step 7: dimsim dev (quick test) ---" +dimsim dev --scene apt --port 8090 & +DEV_PID=$! + +# Wait for server to start +for i in $(seq 1 15); do + if curl -s http://localhost:8090 > /dev/null 2>&1; then + echo "Server responding after ${i}s" + break + fi + if [ "$i" -eq 15 ]; then + echo "FAIL: Server never started" + kill $DEV_PID 2>/dev/null || true + exit 1 + fi + sleep 1 +done + +# Verify HTML response contains DimSim +RESP=$(curl -s http://localhost:8090) +if echo "$RESP" | grep -q "dimosMode"; then + echo "HTML contains dimosMode injection — correct!" +else + echo "FAIL: HTML missing dimosMode" + kill $DEV_PID 2>/dev/null || true + exit 1 +fi + +# Verify scene asset is served +STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8090/sims/apt.json) +if [ "$STATUS" = "200" ]; then + echo "Scene apt.json served — HTTP 200" +else + echo "FAIL: Scene not served (HTTP $STATUS)" + kill $DEV_PID 2>/dev/null || true + exit 1 +fi + +kill $DEV_PID 2>/dev/null || true + +echo "" +echo "=== ALL TESTS PASSED ===" diff --git a/misc/DimSim/evals/apt/go-to-couch.json b/misc/DimSim/evals/apt/go-to-couch.json new file mode 100644 index 0000000000..74d4535487 --- /dev/null +++ b/misc/DimSim/evals/apt/go-to-couch.json @@ -0,0 +1,19 @@ +{ + "name": "go-to-couch", + "environment": "apt", + "task": "Go to the couch", + "startPose": { + "x": 0, + "y": 0.5, + "z": 3, + "yaw": 0 + }, + "timeoutSec": 30, + "successCriteria": { + "objectDistance": { + "object": "agent", + "target": "sectional", + "thresholdM": 2.0 + } + } +} diff --git a/misc/DimSim/evals/apt/go-to-kitchen.json b/misc/DimSim/evals/apt/go-to-kitchen.json new file mode 100644 index 0000000000..7e68734583 --- /dev/null +++ b/misc/DimSim/evals/apt/go-to-kitchen.json @@ -0,0 +1,19 @@ +{ + "name": "go-to-kitchen", + "environment": "apt", + "task": "Go to the kitchen", + "startPose": { + "x": 0, + "y": 0.5, + "z": 3, + "yaw": 0 + }, + "timeoutSec": 30, + "successCriteria": { + "objectDistance": { + "object": "agent", + "target": "refrigerator", + "thresholdM": 3.0 + } + } +} diff --git a/misc/DimSim/evals/apt/go-to-tv.json b/misc/DimSim/evals/apt/go-to-tv.json new file mode 100644 index 0000000000..ba07f96167 --- /dev/null +++ b/misc/DimSim/evals/apt/go-to-tv.json @@ -0,0 +1,19 @@ +{ + "name": "go-to-tv", + "environment": "apt", + "task": "Go to the TV", + "startPose": { + "x": 0, + "y": 0.5, + "z": 3, + "yaw": 0 + }, + "timeoutSec": 30, + "successCriteria": { + "objectDistance": { + "object": "agent", + "target": "television", + "thresholdM": 2.0 + } + } +} diff --git a/misc/DimSim/evals/apt/television.json b/misc/DimSim/evals/apt/television.json new file mode 100644 index 0000000000..450375e524 --- /dev/null +++ b/misc/DimSim/evals/apt/television.json @@ -0,0 +1,19 @@ +{ + "name": "television", + "environment": "apt", + "task": "Go to the Television", + "startPose": { + "x": 0, + "y": 0.5, + "z": 3, + "yaw": 0 + }, + "timeoutSec": 60, + "successCriteria": { + "objectDistance": { + "object": "agent", + "target": "television", + "thresholdM": 2 + } + } +} diff --git a/misc/DimSim/evals/manifest.json b/misc/DimSim/evals/manifest.json new file mode 100644 index 0000000000..8e34d570b5 --- /dev/null +++ b/misc/DimSim/evals/manifest.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "environments": [ + { + "name": "apt", + "scene": "apt", + "workflows": [ + "go-to-tv", + "go-to-couch", + "go-to-kitchen", + "television" + ] + } + ] +} diff --git a/misc/DimSim/index.html b/misc/DimSim/index.html new file mode 100644 index 0000000000..9d4f457b75 --- /dev/null +++ b/misc/DimSim/index.html @@ -0,0 +1,212 @@ + + + + + + DimSim + + + + + + + + + + +
+ +
+ + + + + + + +
+
+ DimSim + +
+ +
+ +
+
+ + +
+
No scene loaded
+ + +
+ + + +
+
+ Sensor Debug +
+ + +
+
+ +
+
+ + +
+
+
+ Min + + 0.2m +
+
+ Max + + 12.0m +
+
+
+ +
+
+ +
+
+ + +
+
+
+ + +
+ Agent Vision +
+
RGB
+ Agent POV +
Depth
+ +
LiDAR
+ +
+
+ +
Waiting...
+
+
+
+ Request Details +
+
No request yet
+
+ Prompt +

+              
+
+ Context +

+              
+
+ Raw Output +

+              
+
+
+
+ Activity Log +
+
+
+ + +
+ + + +
+ + +
+ + + +
+ + + + + +
+ WASD move + E interact + B spawn + G ghost +
+ +
+ + + + + + diff --git a/misc/DimSim/package-lock.json b/misc/DimSim/package-lock.json new file mode 100644 index 0000000000..3478389ba2 --- /dev/null +++ b/misc/DimSim/package-lock.json @@ -0,0 +1,2120 @@ +{ + "name": "dimsim", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dimsim", + "version": "1.0.0", + "dependencies": { + "@dimforge/rapier3d-compat": "^0.14.0", + "@sparkjsdev/spark": "latest", + "cors": "^2.8.5", + "express": "^4.21.0", + "openai": "^4.77.0", + "three": "^0.168.0" + }, + "devDependencies": { + "vite": "^5.4.10" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.14.0.tgz", + "integrity": "sha512-/uHrUzS+CRQ+NQrrJCEDUkhwHlNsAAexbNXgbN9sHY+GwR+SFFAFrxRr8Llf5/AJZzqiLANdQIfJ63Cw4gJVqw==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sparkjsdev/spark": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@sparkjsdev/spark/-/spark-0.1.10.tgz", + "integrity": "sha512-CiijdZQuj7KPDUqIZPiEqyUkJCYo1JqR05vq/V+ElxMwqR7L70ZuZDyIKcasjZHSiPB8pGRMH8HZGqUKO9aRPQ==", + "license": "MIT", + "dependencies": { + "fflate": "^0.8.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/three": { + "version": "0.168.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.168.0.tgz", + "integrity": "sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/misc/DimSim/package.json b/misc/DimSim/package.json new file mode 100644 index 0000000000..abdc9269cf --- /dev/null +++ b/misc/DimSim/package.json @@ -0,0 +1,30 @@ +{ + "name": "dimsim", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "Standalone 3D simulation runner for SimStudio scenes", + "scripts": { + "dev": "vite --force", + "build": "vite build", + "preview": "vite preview", + "server": "node server.js", + "sync": "bash copy-sources.sh", + "parity:check": "bash check-parity.sh", + "update-sims": "bash update-sims.sh", + "dimos": "deno run --allow-all dimos-cli/cli.ts eval", + "dimos:headless": "deno run --allow-all dimos-cli/cli.ts eval --headless", + "dimos:dev": "deno run --allow-all dimos-cli/cli.ts dev" + }, + "dependencies": { + "@sparkjsdev/spark": "latest", + "@dimforge/rapier3d-compat": "^0.14.0", + "three": "^0.168.0", + "express": "^4.21.0", + "cors": "^2.8.5", + "openai": "^4.77.0" + }, + "devDependencies": { + "vite": "^5.4.10" + } +} diff --git a/misc/DimSim/public/agent-model/robot.glb b/misc/DimSim/public/agent-model/robot.glb new file mode 100644 index 0000000000..79296152a2 --- /dev/null +++ b/misc/DimSim/public/agent-model/robot.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:761813170c01429ab9215ad4e37b95a768437fc0c6aa1d643f5938a672263322 +size 55746312 diff --git a/misc/DimSim/public/logo.svg b/misc/DimSim/public/logo.svg new file mode 100644 index 0000000000..d64b96adfc --- /dev/null +++ b/misc/DimSim/public/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/misc/DimSim/public/sims/apt.json b/misc/DimSim/public/sims/apt.json new file mode 100644 index 0000000000..fe9be506f7 --- /dev/null +++ b/misc/DimSim/public/sims/apt.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44d3dd75c18a55f02cf2e221f89769ee84f0607ffcc266b31fe9d2f4bc0ac214 +size 102228474 diff --git a/misc/DimSim/public/sims/apt_.json b/misc/DimSim/public/sims/apt_.json new file mode 100644 index 0000000000..2cd08d4d7b --- /dev/null +++ b/misc/DimSim/public/sims/apt_.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ee06fcd4ca045e069828733d767367c72fab8c2c3b6fb6b8a60d3b81759888c +size 102258137 diff --git a/misc/DimSim/public/sims/empty.json b/misc/DimSim/public/sims/empty.json new file mode 100644 index 0000000000..c620dc13fb --- /dev/null +++ b/misc/DimSim/public/sims/empty.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87ce082c5e3c7e88342b1781b494e9b2a9bb8bf080590c75b1d3924604e15e52 +size 3100 diff --git a/misc/DimSim/public/sims/manifest.json b/misc/DimSim/public/sims/manifest.json new file mode 100644 index 0000000000..903a9c41d6 --- /dev/null +++ b/misc/DimSim/public/sims/manifest.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3050b1e85fb6d72fb444b51adb171205ca560eddaf9316ffce74895e003a33d +size 12 diff --git a/misc/DimSim/scenes.template.json b/misc/DimSim/scenes.template.json new file mode 100644 index 0000000000..2bf30efd2f --- /dev/null +++ b/misc/DimSim/scenes.template.json @@ -0,0 +1,9 @@ +{ + "scenes": { + "apt": { + "description": "Modern apartment interior", + "size": 71000000 + } + }, + "evalsUrl": "" +} diff --git a/misc/DimSim/scripts/package-release.sh b/misc/DimSim/scripts/package-release.sh new file mode 100755 index 0000000000..b823f82e7e --- /dev/null +++ b/misc/DimSim/scripts/package-release.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates release artifacts for DimSim distribution. +# dimsim-core-v{VERSION}.tar.gz — frontend bundle (no scenes) +# scene-apt-v{VERSION}.tar.gz — apt scene (gzipped JSON) + +cd "$(dirname "$0")/.." + +VERSION=${1:-"0.1.0"} + +echo "Packaging DimSim v${VERSION}..." + +# Verify dist/ exists +if [ ! -f "dist/index.html" ]; then + echo "Error: dist/ not found. Run 'npm run build' first." + exit 1 +fi + +# Core: everything in dist/ except sims/ +echo "Packaging core assets..." +tar -czf "dimsim-core-v${VERSION}.tar.gz" \ + -C dist \ + --exclude='sims' \ + . + +echo "Packaging apt scene..." +gzip -c dist/sims/apt.json > "scene-apt-v${VERSION}.tar.gz" + +echo "Packaging evals..." +tar -czf "dimsim-evals-v${VERSION}.tar.gz" \ + -C evals \ + . + +echo "" +echo "Release artifacts:" +ls -lh "dimsim-core-v${VERSION}.tar.gz" "scene-apt-v${VERSION}.tar.gz" "dimsim-evals-v${VERSION}.tar.gz" + +echo "" +echo "Upload to GitHub Release:" +echo " gh release create v${VERSION} dimsim-core-v${VERSION}.tar.gz scene-apt-v${VERSION}.tar.gz dimsim-evals-v${VERSION}.tar.gz" diff --git a/misc/DimSim/scripts/profile-live.sh b/misc/DimSim/scripts/profile-live.sh new file mode 100755 index 0000000000..8bb187c933 --- /dev/null +++ b/misc/DimSim/scripts/profile-live.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Simple profiler: one-line-per-sample, appends to terminal (no clearing). +# Usage: bash scripts/profile-live.sh [interval_seconds] + +INTERVAL=${1:-3} + +printf "%-6s %8s %8s %8s %8s %8s %8s %8s %8s %s\n" \ + "TIME" "PY_CPU%" "PY_MB" "DENO_CPU%" "DENO_MB" "SIM_UI%" "SIM_UI_MB" "GPU_CPU%" "GPU_MB" "LOAD" +printf "%-6s %8s %8s %8s %8s %8s %8s %8s %8s %s\n" \ + "------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" + +while true; do + py_cpu=0; py_mem=0 + while IFS= read -r line; do + c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') + py_cpu=$(awk "BEGIN{print $py_cpu + $c}") + py_mem=$(awk "BEGIN{print $py_mem + $m}") + done < <(ps -eo %cpu,rss,command | grep -i '[p]ython.*dimos' 2>/dev/null) + + deno_cpu=0; deno_mem=0 + while IFS= read -r line; do + c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') + deno_cpu=$(awk "BEGIN{print $deno_cpu + $c}") + deno_mem=$(awk "BEGIN{print $deno_mem + $m}") + done < <(ps -eo %cpu,rss,command | grep -E '[d]eno|[d]imsim' 2>/dev/null | grep -v grep) + + # Find browser PIDs connected to bridge port 8090 (exclude deno server itself) + chrome_cpu=0; chrome_mem=0 + while IFS= read -r pid; do + [ -z "$pid" ] && continue + while IFS= read -r line; do + c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') + chrome_cpu=$(awk "BEGIN{print $chrome_cpu + $c}") + chrome_mem=$(awk "BEGIN{print $chrome_mem + $m}") + done < <(ps -p "$pid" -o %cpu,rss 2>/dev/null | tail -n +2) + done < <(lsof -i :8090 2>/dev/null | grep -v 'deno\|LISTEN' | awk 'NR>1{print $2}' | sort -u) + + # GPU processes — macOS Metal/WindowServer GPU usage + gpu_cpu=0; gpu_mem=0 + while IFS= read -r line; do + c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') + gpu_cpu=$(awk "BEGIN{print $gpu_cpu + $c}") + gpu_mem=$(awk "BEGIN{print $gpu_mem + $m}") + done < <(ps -eo %cpu,rss,command | grep -E '[W]indowServer|[G]PU.*Driver|com\.apple\.gpu' 2>/dev/null) + + py_mb=$(awk "BEGIN{printf \"%.0f\", $py_mem/1024}") + deno_mb=$(awk "BEGIN{printf \"%.0f\", $deno_mem/1024}") + chrome_mb=$(awk "BEGIN{printf \"%.0f\", $chrome_mem/1024}") + gpu_mb=$(awk "BEGIN{printf \"%.0f\", $gpu_mem/1024}") + load=$(sysctl -n vm.loadavg 2>/dev/null | awk '{print $2}' || echo "?") + + printf "%-6s %8.1f %6sMB %8.1f %6sMB %8.1f %6sMB %8.1f %6sMB %s\n" \ + "$(date '+%H:%M:%S')" "$py_cpu" "$py_mb" "$deno_cpu" "$deno_mb" "$chrome_cpu" "$chrome_mb" "$gpu_cpu" "$gpu_mb" "$load" + + sleep "$INTERVAL" +done diff --git a/misc/DimSim/scripts/speed-test.py b/misc/DimSim/scripts/speed-test.py new file mode 100644 index 0000000000..4ce143a8b6 --- /dev/null +++ b/misc/DimSim/scripts/speed-test.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Speed test: send constant cmd_vel for N seconds, measure actual displacement via odom. + +Usage (from dimos venv): + python DimSim/scripts/speed-test.py [--speed 0.5] [--duration 5] + +Requires: dimos environment with LCM working. +Run while DimSim bridge is active (browser tab open). +""" + +import argparse +import math +import struct +import threading +import time + +# LCM multicast constants +LCM_ADDR = "239.255.76.67" +LCM_PORT = 7667 +MAGIC = 0x4C433032 + +# ── Minimal LCM encode/decode (no dimos import needed) ───────────────────── + + +def _encode_lcm_packet(channel: str, payload: bytes, seq: int) -> bytes: + ch_bytes = channel.encode("utf-8") + header = struct.pack(">II", MAGIC, seq) + return header + ch_bytes + b"\x00" + payload + + +def _decode_lcm_packet(data: bytes): + if len(data) < 8: + return None, None + magic, seq = struct.unpack(">II", data[:8]) + if magic != MAGIC: + return None, None + rest = data[8:] + null_idx = rest.index(0) + channel = rest[:null_idx].decode("utf-8") + payload = rest[null_idx + 1 :] + return channel, payload + + +# ── Twist encoding (geometry_msgs.Twist LCM) ─────────────────────────────── +# Layout: hash(8) + 6 x float64 (linear.x,y,z, angular.x,y,z) + + +def encode_twist( + linear_x=0.0, linear_y=0.0, linear_z=0.0, angular_x=0.0, angular_y=0.0, angular_z=0.0 +) -> bytes: + from dimos.msgs.geometry_msgs import Twist, Vector3 + + t = Twist( + linear=Vector3(x=linear_x, y=linear_y, z=linear_z), + angular=Vector3(x=angular_x, y=angular_y, z=angular_z), + ) + return t.lcm_encode() + + +# ── PoseStamped decoding ─────────────────────────────────────────────────── + + +def decode_pose_stamped(data: bytes): + """Decode geometry_msgs.PoseStamped using dimos LCM decoder.""" + from dimos.msgs.geometry_msgs import PoseStamped + + msg = PoseStamped.lcm_decode(data) + p = msg.position + q = msg.orientation + return p.x, p.y, p.z, q.x, q.y, q.z, q.w + + +# ── Main ──────────────────────────────────────────────────────────────────── + + +def main(): + import socket + + parser = argparse.ArgumentParser(description="DimSim speed test") + parser.add_argument("--speed", type=float, default=0.5, help="linear.x m/s (default 0.5)") + parser.add_argument("--duration", type=float, default=5.0, help="seconds to drive (default 5)") + args = parser.parse_args() + + speed = args.speed + duration = args.duration + + # ── Setup UDP multicast socket ── + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + sock.bind(("", LCM_PORT)) + + mreq = struct.pack("4s4s", socket.inet_aton(LCM_ADDR), socket.inet_aton("0.0.0.0")) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + sock.settimeout(0.1) + + # Send socket (separate so we can send without receiving our own) + send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) + + # ── Collect initial odom ── + print("Waiting for odom...") + odom_channel = "/odom#geometry_msgs.PoseStamped" + start_pose = None + + for _ in range(200): # up to 20s + try: + data, _ = sock.recvfrom(65535) + except TimeoutError: + continue + ch, payload = _decode_lcm_packet(data) + if ch == odom_channel: + start_pose = decode_pose_stamped(payload) + break + + if start_pose is None: + print("ERROR: No odom received. Is the bridge running?") + return + + sx, sy, sz = start_pose[0], start_pose[1], start_pose[2] + print(f"Start pose: ({sx:.3f}, {sy:.3f}, {sz:.3f})") + print(f"Sending cmd_vel: linear.x={speed} m/s for {duration}s") + print(f"Expected displacement: {speed * duration:.2f}m") + print() + + # ── Drive: send cmd_vel at 10 Hz, collect odom ── + seq = 1000 + stop = threading.Event() + poses = [] + + def send_cmd_vel(): + nonlocal seq + while not stop.is_set(): + twist_data = encode_twist(linear_x=speed) + packet = _encode_lcm_packet("/cmd_vel#geometry_msgs.Twist", twist_data, seq) + send_sock.sendto(packet, (LCM_ADDR, LCM_PORT)) + seq += 1 + time.sleep(0.1) # 10 Hz, matches planner rate + + sender = threading.Thread(target=send_cmd_vel, daemon=True) + sender.start() + + t0 = time.time() + while time.time() - t0 < duration: + try: + data, _ = sock.recvfrom(65535) + except TimeoutError: + continue + ch, payload = _decode_lcm_packet(data) + if ch == odom_channel: + pose = decode_pose_stamped(payload) + elapsed = time.time() - t0 + poses.append((elapsed, pose)) + + # Stop sending, send zero velocity + stop.set() + sender.join() + for _ in range(5): + twist_data = encode_twist() + packet = _encode_lcm_packet("/cmd_vel#geometry_msgs.Twist", twist_data, seq) + send_sock.sendto(packet, (LCM_ADDR, LCM_PORT)) + seq += 1 + time.sleep(0.05) + + # ── Results ── + if not poses: + print("ERROR: No odom received during test") + return + + last_pose = poses[-1][1] + ex, ey, ez = last_pose[0], last_pose[1], last_pose[2] + dx, dy, dz = ex - sx, ey - sy, ez - sz + dist = math.sqrt(dx * dx + dy * dy + dz * dz) + expected = speed * duration + ratio = dist / expected if expected > 0 else 0 + + print(f"{'─' * 50}") + print(f"End pose: ({ex:.3f}, {ey:.3f}, {ez:.3f})") + print(f"Displacement: {dist:.3f}m") + print(f"Expected: {expected:.3f}m") + print( + f"Ratio: {ratio:.2f}x {'(OK)' if 0.85 < ratio < 1.15 else '(SLOW!)' if ratio < 0.85 else '(FAST!)'}" + ) + print(f"Odom samples: {len(poses)} ({len(poses) / duration:.0f} Hz)") + print(f"{'─' * 50}") + + # Show velocity over time + print("\nVelocity trace (sampled):") + prev_t, prev_p = 0, start_pose + for i, (t, p) in enumerate(poses): + if i % max(1, len(poses) // 10) != 0: + continue + dt = t - prev_t + if dt > 0: + d = math.sqrt( + (p[0] - prev_p[0]) ** 2 + (p[1] - prev_p[1]) ** 2 + (p[2] - prev_p[2]) ** 2 + ) + v = d / dt + print(f" t={t:5.2f}s v={v:.3f} m/s pos=({p[0]:.3f}, {p[1]:.3f}, {p[2]:.3f})") + prev_t, prev_p = t, p + + sock.close() + send_sock.close() + + +if __name__ == "__main__": + main() diff --git a/misc/DimSim/server.js b/misc/DimSim/server.js new file mode 100644 index 0000000000..9e45ee4663 --- /dev/null +++ b/misc/DimSim/server.js @@ -0,0 +1,174 @@ +import express from "express"; +import cors from "cors"; +import OpenAI from "openai"; +import { createHash } from "crypto"; +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// ── API Keys (from environment variables) ─────────────────────────────────── +const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ""; +const GEMINI_API_KEY = process.env.GEMINI_API_KEY || ""; +const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"; +const VLM_MIN_INTERVAL_MS = 900; +const VLM_MAX_RETRIES = 4; +let _lastVlmAt = 0; + +// ── Clients ───────────────────────────────────────────────────────────────── +function getClient(model) { + if (model.startsWith("gemini")) { + return new OpenAI({ apiKey: GEMINI_API_KEY, baseURL: GEMINI_BASE_URL }); + } + return new OpenAI({ apiKey: OPENAI_API_KEY }); +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function paceVlmRequests() { + const now = Date.now(); + const waitMs = Math.max(0, _lastVlmAt + VLM_MIN_INTERVAL_MS - now); + if (waitMs > 0) await sleep(waitMs); + _lastVlmAt = Date.now(); +} + +async function requestWithRetry(runRequest) { + let attempt = 0; + let lastErr = null; + while (attempt < VLM_MAX_RETRIES) { + try { + await paceVlmRequests(); + return await runRequest(); + } catch (err) { + lastErr = err; + const status = Number(err?.status || err?.statusCode || 0); + const retryAfterHeader = + Number(err?.headers?.["retry-after"] || err?.response?.headers?.get?.("retry-after") || 0) || 0; + const shouldRetry = status === 429 || status === 503 || status === 504; + attempt += 1; + if (!shouldRetry || attempt >= VLM_MAX_RETRIES) break; + const backoff = Math.min(6000, 600 * 2 ** (attempt - 1)); + const retryAfterMs = retryAfterHeader > 0 ? retryAfterHeader * 1000 : 0; + const jitterMs = Math.floor(Math.random() * 250); + await sleep(Math.max(backoff, retryAfterMs) + jitterMs); + } + } + throw lastErr; +} + +// ── Asset library file ────────────────────────────────────────────────────── +const ASSET_LIBRARY_FILE = join(__dirname, "vlm-server", "asset-library.json"); + +// ── App ───────────────────────────────────────────────────────────────────── +const app = express(); +app.use(cors()); +app.use(express.json({ limit: "100mb" })); + +// ── POST /vlm/decision ────────────────────────────────────────────────────── +app.post("/vlm/decision", async (req, res) => { + try { + const { model, prompt, imageBase64, context, messages: reqMessages, max_tokens } = req.body; + const client = getClient(model); + + let messages = reqMessages; + if (!messages) { + const userContent = imageBase64 + ? [ + { type: "text", text: `Context:\n${JSON.stringify(context || {})}` }, + { type: "image_url", image_url: { url: `data:image/jpeg;base64,${imageBase64}` } }, + ] + : `Context:\n${JSON.stringify(context || {})}`; + messages = [ + { role: "system", content: prompt || "You are an AI agent. Output JSON only." }, + { role: "user", content: userContent }, + ]; + } + + const maxTok = max_tokens || 16384; + const params = { model, messages, temperature: 0.3 }; + if (model.startsWith("gemini")) { + params.max_tokens = maxTok; + } else { + params.max_completion_tokens = maxTok; + } + + const response = await requestWithRetry(() => client.chat.completions.create(params)); + const text = response.choices?.[0]?.message?.content || ""; + res.json({ raw: text }); + } catch (err) { + const status = Number(err?.status || err?.statusCode || 500); + const safeStatus = Number.isFinite(status) && status >= 400 && status <= 599 ? status : 500; + const msg = err?.message || "VLM request failed"; + console.error("[/vlm/decision]", safeStatus, msg); + res.status(safeStatus).json({ detail: msg }); + } +}); + +// ── POST /vlm/generate-image ──────────────────────────────────────────────── +app.post("/vlm/generate-image", async (req, res) => { + try { + const { prompt, size = "1024x1024", quality = "medium" } = req.body; + const client = new OpenAI({ apiKey: OPENAI_API_KEY }); + const fullPrompt = `Seamless tileable texture for 3D rendering, top-down flat view, no perspective: ${prompt}`; + + let lastError = null; + for (const modelName of ["gpt-image-1-mini", "gpt-image-1"]) { + try { + const response = await client.images.generate({ model: modelName, prompt: fullPrompt, size, quality, n: 1 }); + const b64 = response.data?.[0]?.b64_json; + if (b64) return res.json({ b64, dataUrl: `data:image/png;base64,${b64}`, model: modelName }); + } catch (err) { + lastError = err.message; + continue; + } + } + + const hash = createHash("sha256").update(fullPrompt).digest("hex"); + const h = parseInt(hash.slice(0, 8), 16); + const hue = h % 360; + const hue2 = (hue + 34) % 360; + const svg = ` + + +`; + res.json({ b64: null, dataUrl: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg), fallback: true, error: lastError }); + } catch (err) { + console.error("[/vlm/generate-image]", err.message); + res.status(500).json({ detail: err.message }); + } +}); + +// ── GET /vlm/asset-library ────────────────────────────────────────────────── +app.get("/vlm/asset-library", (req, res) => { + try { + if (existsSync(ASSET_LIBRARY_FILE)) { + const data = JSON.parse(readFileSync(ASSET_LIBRARY_FILE, "utf-8")); + if (Array.isArray(data)) return res.json({ assets: data }); + } + } catch (err) { + console.error("[asset-library] read failed:", err.message); + } + res.json({ assets: [] }); +}); + +// ── POST /vlm/asset-library ───────────────────────────────────────────────── +app.post("/vlm/asset-library", (req, res) => { + try { + const { assets } = req.body; + writeFileSync(ASSET_LIBRARY_FILE, JSON.stringify(assets), "utf-8"); + res.json({ ok: true, count: assets?.length || 0 }); + } catch (err) { + console.error("[asset-library] write failed:", err.message); + res.status(500).json({ detail: err.message }); + } +}); + +// ── Start ─────────────────────────────────────────────────────────────────── +const PORT = process.env.PORT || 8000; +app.listen(PORT, "127.0.0.1", () => { + console.log(`DimSim VLM server running on http://127.0.0.1:${PORT}`); +}); diff --git a/misc/DimSim/src/AiAvatar.js b/misc/DimSim/src/AiAvatar.js new file mode 100644 index 0000000000..cd1075ae6d --- /dev/null +++ b/misc/DimSim/src/AiAvatar.js @@ -0,0 +1,1507 @@ +import * as THREE from "three"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; + +/** + * AiAvatar (SimStudio) + * - Vanilla THREE (no React) + * - Uses Rapier KinematicCharacterController for collision-aware movement + * - "Awareness" of tagged areas via `getTags()` and a sensing radius + * - Persistent memory stored in localStorage per (worldKey, agentId) + */ +export class AiAvatar { + constructor({ + id = null, + scene, + rapierWorld, + RAPIER, + getWorldKey, + getTags, + getPlayerPosition, + avatarUrl = "/avatars/kai.glb", + senseRadius = 3.0, + walkSpeed = 2.0, + vlm = null, + headless = false, + }) { + this.id = id || `ai-${Math.random().toString(36).slice(2, 8)}`; + this.scene = scene; + this.rapierWorld = rapierWorld; + this.RAPIER = RAPIER; + this.getWorldKey = getWorldKey || (() => "default"); + this.getTags = getTags || (() => []); + this.getPlayerPosition = getPlayerPosition || (() => [0, 0, 0]); + this.avatarUrl = avatarUrl; + this.senseRadius = senseRadius; + this.walkSpeed = walkSpeed; + this.vlm = vlm; // { enabled, endpoint, model, buildPrompt, actions, captureBase64, decideEverySteps, stepMeters } + this.headless = headless; // Skip visual rendering in headless mode, keep colliders + + // AI dimensions (match the smaller player so the agent fits in tight interiors too). + this.radius = 0.12; + this.halfHeight = 0.25; + + // Look pitch (radians). Used for agent POV capture to allow looking up/down. + // Kept separate from group rotation (yaw) so walking remains on XZ. + this.pitch = 0; + + this.group = new THREE.Group(); + this.group.name = `AiAvatar:${this.id}`; + this.scene.add(this.group); + + // Fallback visual (if GLB fails) + this.fallback = new THREE.Mesh( + new THREE.CapsuleGeometry(this.radius * 0.9, this.halfHeight * 2.0, 6, 10), + new THREE.MeshStandardMaterial({ color: 0x8cffc1, roughness: 0.65 }) + ); + this.fallback.castShadow = false; + this.fallback.receiveShadow = false; + this.group.add(this.fallback); + + // Facing indicator (always present so direction is obvious even with capsule / GLB). + // Cone points along +Z (our forward convention in this project). + this._facing = new THREE.Mesh( + new THREE.ConeGeometry(this.radius * 0.45, this.radius * 1.35, 14), + new THREE.MeshStandardMaterial({ color: 0xffc36a, roughness: 0.45, metalness: 0.05 }) + ); + this._facing.rotation.x = Math.PI / 2; + this._facing.position.set(0, this.halfHeight * 0.15, this.radius * 1.1); + this._facing.castShadow = false; + this._facing.receiveShadow = false; + this._facing.renderOrder = 10; + this.group.add(this._facing); + + // Thought label (simple sprite) + this._labelCanvas = document.createElement("canvas"); + this._labelCanvas.width = 512; + this._labelCanvas.height = 512; + this._labelCtx = this._labelCanvas.getContext("2d"); + this._labelTex = new THREE.CanvasTexture(this._labelCanvas); + this._labelSprite = new THREE.Sprite( + new THREE.SpriteMaterial({ + map: this._labelTex, + transparent: true, + depthWrite: false, + depthTest: false, // always visible (splats/voxels can otherwise cover it) + toneMapped: false, + }) + ); + this._labelSprite.scale.set(2.0, 1.0, 1); + this._labelSprite.position.set(0, this.halfHeight * 2 + this.radius + 0.8, 0); + this._labelSprite.renderOrder = 5000; // after SparkRenderer (999) + this.group.add(this._labelSprite); + this._setThought(""); + this._lastDecisionBubbleAt = 0; + + this._target = null; // THREE.Vector3 + this._state = "IDLE"; // IDLE | WALK | INSPECT + this._stateUntil = 0; + this._nearbyTagId = null; + this._lastInspectAtByTagId = {}; + this._inspectCooldownMs = 8000; // don't repeatedly "stop" while standing inside a big tag radius + + // VLM action plan + this._plan = null; // { type, ... } + this._planRemaining = 0; + this._stepCounter = 0; + this._nextDecisionAt = 0; + this._decisionJitterMs = (Math.random() * 350) | 0; + this._vlmInFlight = null; + this._pendingDecision = null; + + // Short-term trace for the current task run (fed back to the VLM). + this._taskStartedAt = 0; + this._trace = []; // [{t,type,msg,data?}] + this._traceLimit = 20; + // Prior model outputs for the current task run (fed back as assistant messages). + this._vlmAssistantHistory = []; // string[] + this._vlmAssistantHistoryLimit = 15; + this._moveStepAcc = 0; + this._turnAcc = 0; + + // Task state tracking + this._startPosition = null; // {x,y,z} - where agent was when task started + this._currentSubgoal = ""; // Current sub-goal from VLM reasoning + this._discoveredItems = []; // Items/assets found during exploration + + // Rapier body/collider/controller + const radius = this.radius; + const halfHeight = this.halfHeight; + this.body = this.rapierWorld.createRigidBody( + this.RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(0, 3, 0) + ); + this.collider = this.rapierWorld.createCollider( + this.RAPIER.ColliderDesc.capsule(halfHeight, radius).setFriction(0.8), + this.body + ); + // Fake robodog body volume: add a horizontal spine capsule to reduce rear clipping. + // Movement still uses the vertical capsule for stable character-controller behavior. + this._spineHalfLen = Math.max(radius * 1.2, 0.13); + this._spineRadius = Math.max(radius * 0.62, 0.07); + // Keep fake volume mostly behind body center so it doesn't "push from the air" in front. + this._spineOffsetBack = Math.max(radius * 2.2, this._spineHalfLen + this._spineRadius + 0.02); + this._spineOffsetY = Math.max(this.halfHeight * 0.35, 0.08); + this.spineCollider = this.rapierWorld.createCollider( + this.RAPIER.ColliderDesc.capsule(this._spineHalfLen, this._spineRadius) + .setFriction(0.8) + .setTranslation(0, this._spineOffsetY, -this._spineOffsetBack) + // Rotate local +Y capsule axis to +Z (dog spine direction at yaw=0). + .setRotation({ x: Math.SQRT1_2, y: 0, z: 0, w: Math.SQRT1_2 }), + this.body + ); + this.boxCollider = null; // Box collider created when GLB loads (matches model dimensions) + this.controller = this.rapierWorld.createCharacterController(0.05); + this.controller.enableAutostep(0.25, 0.15, true); + this.controller.enableSnapToGround(0.5); + this.controller.setSlideEnabled(true); + this.controller.setMaxSlopeClimbAngle((45 * Math.PI) / 180); + this.controller.setMinSlopeSlideAngle((75 * Math.PI) / 180); + + // Persistent memory + this.memory = this._loadMemory(); + + // Best-effort: load GLB visual + this._loadGLB(); + } + + dispose() { + try { + this.scene.remove(this.group); + } catch {} + try { + if (this.boxCollider) this.rapierWorld.removeCollider(this.boxCollider, true); + if (this.spineCollider) this.rapierWorld.removeCollider(this.spineCollider, true); + this.rapierWorld.removeCollider(this.collider, true); + this.rapierWorld.removeRigidBody(this.body); + } catch {} + } + + setPosition(x, y, z) { + this.body.setTranslation({ x, y, z }, true); + this.body.setLinvel({ x: 0, y: 0, z: 0 }, true); + } + + getPosition() { + const p = this.body.translation(); + return [p.x, p.y, p.z]; + } + + update(dt, nowMs) { + if (!this.rapierWorld || !this.body) return; + const now = nowMs ?? Date.now(); + + if (this.mixer) this.mixer.update(dt); + + // Sense tags in radius and update memory. + const p = this.body.translation(); + const tags = this.getTags() || []; + const nearby = []; + for (const t of tags) { + if (!t?.position) continue; + const dx = t.position.x - p.x; + const dy = t.position.y - p.y; + const dz = t.position.z - p.z; + const d = Math.sqrt(dx * dx + dy * dy + dz * dz); + const r = Math.max(Number(t.radius ?? 1.5), this.senseRadius); + if (d <= r) nearby.push({ tag: t, dist: d }); + } + nearby.sort((a, b) => a.dist - b.dist); + + if (nearby.length > 0) { + const top = nearby[0].tag; + this._rememberTag(top); + const topId = top.id || null; + const title = top.title || "(untitled)"; + // Don't continuously overwrite model-output bubble while the VLM is active. + const taskActive = !!this.vlm?.getTask?.()?.active; + if (!this.vlm?.enabled && !taskActive && now - this._lastDecisionBubbleAt > 1200) { + this._setThought(`I see: ${title}`); + } + + // Only enter INSPECT when we newly encounter a tag OR the cooldown elapsed. + // Without this, a large tag radius causes the agent to repeatedly re-enter INSPECT + // and look "stuck" near the tag forever. + const lastAt = (topId && this._lastInspectAtByTagId[topId]) || 0; + const cooldownOk = !topId || now - lastAt > this._inspectCooldownMs; + const newlyEntered = topId && topId !== this._nearbyTagId; + this._nearbyTagId = topId; + if (this._state !== "INSPECT" && (newlyEntered || cooldownOk)) { + if (topId) this._lastInspectAtByTagId[topId] = now; + this._state = "INSPECT"; + this._stateUntil = now + 900; + this._target = null; + } + } else { + this._nearbyTagId = null; + } + + if (this._state === "INSPECT") { + if (now > this._stateUntil) { + this._state = "IDLE"; + } + // When inspecting, gently look toward the closest tag in full 3D (pitch + yaw). + try { + const tags = this.getTags?.() || []; + const p = this.body.translation(); + let best = null; + let bestD = Infinity; + for (const t of tags) { + if (!t?.position) continue; + const dx = t.position.x - p.x; + const dy = t.position.y - (p.y + this.halfHeight); // approximate eye height + const dz = t.position.z - p.z; + const d = Math.hypot(dx, dy, dz); + if (d < bestD) { + bestD = d; + best = { dx, dy, dz }; + } + } + if (best) { + // Yaw toward target (XZ) and pitch toward target (Y). + this.group.rotation.y = Math.atan2(best.dx, best.dz); + const horiz = Math.hypot(best.dx, best.dz) || 1; + const desiredPitch = Math.atan2(best.dy, horiz); + const maxPitch = (85 * Math.PI) / 180; + // Smooth pitch change to avoid snapping. + const alpha = Math.min(1, dt * 8); + const clamped = Math.max(-maxPitch, Math.min(maxPitch, desiredPitch)); + this.pitch = this.pitch + (clamped - this.pitch) * alpha; + } + } catch { + // ignore + } + this._syncVisual(); + this._publishGlobals(); + return; + } + + // If VLM mode is enabled, it drives the movement plan. + if (this.vlm?.enabled) { + this._vlmUpdate(dt, now, nearby); + if (!this._plan) this._applyIdleGravity(dt); + this._syncVisual(); + this._publishGlobals(); + return; + } + + // Editor helper mode: hold position when not actively tasked. + // This prevents idle spawned agents from random-walking before assignment. + if (this.vlm?.holdPositionWhenIdle) { + const task = this.vlm?.getTask?.(); + if (!task?.active || !this.vlm?.enabled) { + this._state = "IDLE"; + this._target = null; + this._setThought(""); + this._applyIdleGravity(dt); + this._syncVisual(); + this._publishGlobals(); + return; + } + } + + // Pick a new wander target if needed. + if (!this._target || this._state === "IDLE") { + this._target = this._pickWanderTarget(p); + this._state = "WALK"; + } + + // Move toward target on XZ plane with collision sliding. + const dx = this._target.x - p.x; + const dz = this._target.z - p.z; + const dist = Math.sqrt(dx * dx + dz * dz); + if (dist < 0.4) { + this._state = "IDLE"; + this._target = null; + this._setThought(""); + this._syncVisual(); + this._publishGlobals(); + return; + } + + const vx = (dx / (dist || 1)) * this.walkSpeed; + const vz = (dz / (dist || 1)) * this.walkSpeed; + const desired = { x: vx * dt, y: -2.0 * dt, z: vz * dt }; // small down force to keep grounded + + // Query pipeline is updated by rapierWorld.step() in the main loop + const m = this._computeConservativeMovement(desired); + const mx = m.x, my = m.y, mz = m.z; + this.body.setNextKinematicTranslation({ x: p.x + mx, y: p.y + my, z: p.z + mz }); + + // Face direction. + const yaw = Math.atan2(vx, vz); + this.group.rotation.y = yaw; + // While walking, ease pitch back to level. + { + const alpha = Math.min(1, dt * 6); + this.pitch = this.pitch + (0 - this.pitch) * alpha; + } + + this._syncVisual(); + this._publishGlobals(); + } + + // --- internals --- + _vlmUpdate(dt, now, nearby) { + const task = this.vlm?.getTask?.(); + // If there is no active instruction, do not call the VLM (keeps UI quiet and enforces one-task-at-a-time). + if (!task?.active) return; + + // Reset per-task trace if a new task started. + const startedAt = Number(task.startedAt || 0); + if (startedAt && startedAt !== this._taskStartedAt) { + this._taskStartedAt = startedAt; + this._trace = []; + this._vlmAssistantHistory = []; + this._stepCounter = 0; + this._plan = null; + this._planRemaining = 0; + this._moveStepAcc = 0; + this._turnAcc = 0; + this._currentSubgoal = ""; + this._discoveredItems = []; + // Record start position for "return to start" navigation + const [sx, sy, sz] = this.getPosition?.() || [0, 0, 0]; + this._startPosition = { x: sx, y: sy, z: sz }; + this._tracePush(now, "TASK_START", { instruction: String(task.instruction || ""), startPos: this._startPosition }); + } + + // Apply any resolved decision. + if (this._pendingDecision) { + const d = this._pendingDecision; + this._pendingDecision = null; + this._applyVlmDecision(d); + } + + // Execute current plan if any. + if (this._plan) { + const done = this._stepPlan(dt); + if (!done) return; + // Plan completed. + { + const [x, y, z] = this.getPosition?.() || [0, 0, 0]; + const yaw = this.group?.rotation?.y ?? 0; + this._tracePush(now, "PLAN_DONE", { plan: this._plan, pose: { x, y, z, yaw } }); + } + this._plan = null; + this._planRemaining = 0; + } + + // Decide periodically (every N executed steps) or when idle. + const decideEverySteps = Number(this.vlm.decideEverySteps ?? 4); + if (now < this._nextDecisionAt) return; + if (this._vlmInFlight) return; + + // Kick off async decision (non-blocking). + this._vlmInFlight = this._requestVlmDecision(now, nearby) + .then((d) => { + this._pendingDecision = d; + }) + .catch((e) => { + // If it fails, wait a bit then try again. + this._nextDecisionAt = now + 2000; + this._setThought(`VLM error`); + try { + this.vlm?.onError?.(e); + } catch {} + console.warn("VLM decision failed:", e); + }) + .finally(() => { + this._vlmInFlight = null; + }); + + // Rate limit: even if we render at 60fps, don't spam. + this._nextDecisionAt = now + Math.max(500, decideEverySteps * 250) + this._decisionJitterMs; + } + + async _requestVlmDecision(now, nearby) { + // Increment step counter once per VLM decision + this._stepCounter++; + + const capture = this.vlm.captureBase64; + const prompt = this.vlm.buildPrompt?.({ actions: this.vlm.actions }) ?? ""; + const model = this.vlm.getModel?.() || this.vlm.model; + const endpoint = this.vlm.endpoint; + + const imageBase64 = await capture(this); + if (!imageBase64) throw new Error("No image."); + try { + this.vlm.onCapture?.(imageBase64); + } catch {} + + const [ax, ay, az] = this.getPosition?.() || [0, 0, 0]; + const yaw = this.group?.rotation?.y ?? 0; + const pitch = typeof this.pitch === "number" ? this.pitch : 0; + + const pose = { x: ax.toFixed(2), y: ay.toFixed(2), z: az.toFixed(2), yaw: (yaw * 180 / Math.PI).toFixed(0) + "°", pitch: (pitch * 180 / Math.PI).toFixed(0) + "°" }; + + // Get nearby assets with simplified info (filter out held items - they're not "nearby") + const nearbyAssets = (this.vlm?.getNearbyAssets?.(this) || []) + .filter(a => !a.isHeld) // Don't show held items in nearby list + .map((a) => ({ + id: a.id, + name: a.title || a.id, + description: a.notes || "", + distance: a.dist.toFixed(1) + "m", + lookingAt: a.isLookedAt, + state: a.currentStateName || a.currentState, + canDo: (a.actions || []).map((x) => x.label).filter(Boolean), + pickable: a.pickable || false, + })); + const nearbyPrimitives = (this.vlm?.getNearbyPrimitives?.(this) || []).map((p) => ({ + id: p.id, + name: p.name || p.id, + distance: p.dist.toFixed(1) + "m", + lookingAt: !!p.isLookedAt, + type: p.type || "primitive", + })); + const assetLibraryNames = (this.vlm?.getAssetLibraryNames?.() || []).slice(0, 12); + const isEditorMode = !!this.vlm?.isEditorMode?.(); + + // Get what the agent is currently holding + const heldAsset = this.vlm?.getHeldAsset?.(this); + const recentGeneratedAssets = (this.vlm?.getRecentGeneratedAssets?.(this) || []).slice(0, 8); + + // Get nearby tags/locations + const nearbyLocations = (nearby || []).slice(0, 6).map((x) => ({ + id: x.tag?.id, + name: x.tag?.title || "unknown", + distance: x.dist.toFixed(1) + "m", + })); + + // Build condensed context for user message + const task = this.vlm?.getTask?.(); + const taskInstruction = String(task?.instruction || "No active task"); + + // Chat-style message history: system prompt + prior assistant outputs + current user (context + image). + const assistantMsgs = (this._vlmAssistantHistory || []) + .slice(-this._vlmAssistantHistoryLimit) + .filter((s) => typeof s === "string" && s.trim().length > 0) + .map((s) => ({ role: "assistant", content: s })); + + // Build concise user message + const lines = [ + `TASK: ${taskInstruction}`, + // `${this._stepCounter}${this._stepCounter === 1 ? 'st' : this._stepCounter === 2 ? 'nd' : this._stepCounter === 3 ? 'rd' : 'th'} ACTION`, + `POSITION: ${pose.x}, ${pose.y}, ${pose.z} | facing ${pose.yaw} yaw, ${pose.pitch} pitch`, + ]; + + if (this._startPosition) { + lines.push(`START POSITION: ${this._startPosition.x.toFixed(1)}, ${this._startPosition.y.toFixed(1)}, ${this._startPosition.z.toFixed(1)}`); + } + + // Show what the agent is currently holding + if (heldAsset) { + lines.push(`\nHOLDING: ${heldAsset.title || heldAsset.id} [id: ${heldAsset.id}]`); + lines.push(` → Use DROP to place it in front of you`); + } + + if (nearbyAssets.length > 0) { + lines.push(`\nNEARBY OBJECTS:`); + for (const a of nearbyAssets) { + const looking = a.lookingAt ? " [LOOKING AT]" : ""; + const pickable = a.pickable ? " [pickable]" : ""; + const portal = a.isPortal ? ` [PORTAL → ${a.destinationWorld || "?"}]` : ""; + const actions = a.canDo.length > 0 ? ` → can: ${a.canDo.join(", ")}` : ""; + // Include the asset ID so the VLM can use it for INTERACT/PICK_UP actions + lines.push(` • ${a.name} [id: ${a.id}] (${a.distance})${looking}${pickable}${portal} - ${a.state}${actions}`); + } + } + if (nearbyPrimitives.length > 0) { + lines.push(`\nNEARBY PRIMITIVES:`); + for (const p of nearbyPrimitives) { + const looking = p.lookingAt ? " [LOOKING AT]" : ""; + lines.push(` • ${p.name} [id: ${p.id}] (${p.distance})${looking} - ${p.type}`); + } + } + if (recentGeneratedAssets.length > 0) { + lines.push(`\nRECENT GENERATED ASSETS (transform these by ID even if not in nearby list):`); + for (const a of recentGeneratedAssets) { + const name = String(a?.name || "generated-asset"); + const id = String(a?.id || ""); + if (!id) continue; + lines.push(` • ${name} [id: ${id}]`); + } + } + + if (nearbyLocations.length > 0) { + lines.push(`\nNEARBY LOCATIONS:`); + for (const loc of nearbyLocations) { + lines.push(` • ${loc.name} (${loc.distance}) [id: ${loc.id}]`); + } + } + lines.push(`\nEDITOR MODE: ${isEditorMode ? "ON" : "OFF"}`); + if (isEditorMode && assetLibraryNames.length > 0) { + lines.push(`ASSET LIBRARY: ${assetLibraryNames.join(", ")}`); + } + + const userText = lines.join("\n"); + + // Keep context object for backwards compatibility but simplified + const context = { + task: { instruction: taskInstruction, active: !!task?.active }, + pose: { x: ax, y: ay, z: az, yaw, pitch }, + step: this._stepCounter, + startPosition: this._startPosition, + editorMode: isEditorMode, + nearbyAssets, + nearbyPrimitives, + assetLibraryNames, + nearbyLocations, + heldAsset: heldAsset ? { id: heldAsset.id, title: heldAsset.title } : null, + }; + + const messages = [ + { role: "system", content: prompt }, + ...assistantMsgs, + { + role: "user", + content: [ + { type: "text", text: userText }, + { type: "image_url", image_url: { url: `data:image/jpeg;base64,${imageBase64}` } }, + ], + }, + ]; + + try { + this.vlm?.onRequest?.({ endpoint, model, prompt, context, imageBase64, messages }); + } catch {} + + const res = await this.vlm.request({ + endpoint, + model, + prompt, + imageBase64, + context, + messages, + }); + + // Server returns {raw: "..."} or direct JSON object. + const raw = res?.raw ?? res; + // Persist raw model output into per-task assistant history for next turn context. + try { + const rawStr = typeof raw === "string" ? raw : JSON.stringify(raw); + this._vlmAssistantHistory.push(rawStr); + if (this._vlmAssistantHistory.length > this._vlmAssistantHistoryLimit) { + this._vlmAssistantHistory.splice(0, this._vlmAssistantHistory.length - this._vlmAssistantHistoryLimit); + } + } catch { + // ignore + } + const parsed = typeof raw === "string" ? safeParseJson(raw) : raw; + if (!parsed || typeof parsed !== "object") throw new Error("Invalid VLM output."); + // Update speech bubble immediately on each model turn (before action application). + try { + const responseBubble = this._extractBubbleTextFromModelOutput(parsed, raw); + if (responseBubble) { + this._setThought(responseBubble); + this._lastDecisionBubbleAt = Date.now(); + } + } catch {} + try { + this.vlm?.onResponse?.({ raw: typeof raw === "string" ? raw : JSON.stringify(raw), parsed }); + } catch {} + return parsed; + } + + _applyVlmDecision(decision) { + // Extract reasoning/thinking for logs and bubble display. + const paramsForBubble = decision?.params && typeof decision.params === "object" ? decision.params : {}; + const thinking = decision.thinking || decision.thought || decision.reasoning || ""; + const observation = + decision.observation || + decision.obs || + decision.perception || + paramsForBubble.observation || + ""; + const subgoal = decision.currentSubgoal || ""; + + // Update current sub-goal tracking + if (subgoal) this._currentSubgoal = subgoal; + + // Display observation bubble (prioritize what the agent sees, not chain-of-thought). + // If observation is absent, show a concise action status so the bubble still updates each turn. + const actionPreview = String(decision?.action || "").trim(); + const paramPreview = Object.entries(paramsForBubble) + .slice(0, 2) + .map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`) + .join(", "); + const fallbackStatus = actionPreview + ? `Action: ${actionPreview}${paramPreview ? ` (${paramPreview})` : ""}` + : subgoal || ""; + const displayText = String(observation || fallbackStatus || "").trim(); + this._setThought(displayText); + this._lastDecisionBubbleAt = Date.now(); + + try { + this.vlm?.onDecision?.(decision); + } catch {} + + const action = String(decision.action || "").trim().toUpperCase(); + const params = decision.params && typeof decision.params === "object" ? decision.params : {}; + + const [x, y, z] = this.getPosition?.() || [0, 0, 0]; + const yaw = this.group?.rotation?.y ?? 0; + this._tracePush(Date.now(), "ACTION", { + action, + params, + thinking: thinking.slice(0, 100), + subgoal, + pose: { x: x.toFixed(2), y: y.toFixed(2), z: z.toFixed(2), yaw: (yaw * 180 / Math.PI).toFixed(0) } + }); + + // Normalize bounds helpers + const clampInt = (v, a, b, d) => { + const n = Math.floor(Number(v)); + return Number.isFinite(n) ? Math.max(a, Math.min(b, n)) : d; + }; + const clampNum = (v, a, b, d) => { + const n = Number(v); + return Number.isFinite(n) ? Math.max(a, Math.min(b, n)) : d; + }; + + // === DONE / FINISH_TASK === + if (action === "DONE" || action === "FINISH_TASK") { + const summary = String(params.summary || "Task completed"); + const confidence = clampNum(params.confidence, 0, 1, 1); + try { + this.vlm?.onTaskFinished?.({ summary, confidence }); + } catch {} + this._tracePush(Date.now(), "TASK_DONE", { summary, confidence }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + + // === THINK - pause to reason === + if (action === "THINK") { + const thought = String(params.thought || thinking || "thinking..."); + // THINK action still surfaces as a visible status, but never truncated. + this._setThought(thought); + this._tracePush(Date.now(), "THINK", { thought }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + + // === INTERACT / INTERACT_ASSET === + if (action === "INTERACT" || action === "INTERACT_ASSET") { + const assetId = String(params.assetId || ""); + // Support both actionLabel (new) and actionId (old) + const actionLabel = String(params.actionLabel || params.actionId || ""); + + console.log(`[AI INTERACT] Raw params: assetId="${assetId}", actionLabel="${actionLabel}"`); + + // Find matching action by label + const nearbyAssets = this.vlm?.getNearbyAssets?.(this) || []; + console.log(`[AI INTERACT] Nearby assets:`, nearbyAssets.map(a => ({ + id: a.id, + title: a.title, + dist: a.dist?.toFixed(2), + isLookedAt: a.isLookedAt, + currentState: a.currentState, + actions: a.actions?.map(act => `${act.id}:${act.label}(${act.from}->${act.to})`) + }))); + + const targetAsset = nearbyAssets.find((a) => a.id === assetId); + let actionId = actionLabel; + + if (targetAsset && targetAsset.actions) { + console.log(`[AI INTERACT] Target asset found: "${targetAsset.title}", looking for action: "${actionLabel}"`); + console.log(`[AI INTERACT] Available actions:`, targetAsset.actions); + + const matchingAction = targetAsset.actions.find( + (a) => a.label?.toLowerCase() === actionLabel.toLowerCase() || a.id === actionLabel + ); + if (matchingAction) { + console.log(`[AI INTERACT] Matched action: "${matchingAction.id}" (label: "${matchingAction.label}")`); + actionId = matchingAction.id; + } else { + console.warn(`[AI INTERACT] No matching action found for label "${actionLabel}"`); + } + } else { + console.warn(`[AI INTERACT] Target asset "${assetId}" not found in nearby assets`); + } + + console.log(`[AI INTERACT] Final actionId to use: "${actionId}"`); + this._tracePush(Date.now(), "INTERACT", { assetId, actionLabel, actionId }); + + Promise.resolve() + .then(() => this.vlm?.interactAsset?.({ agent: this, assetId, actionId })) + .then((res) => { + if (res?.ok) { + this._tracePush(Date.now(), "INTERACT_OK", { assetId, actionLabel }); + this._discoveredItems.push({ id: assetId, interacted: true, at: Date.now() }); + } else { + this._tracePush(Date.now(), "INTERACT_FAIL", { assetId, actionLabel, reason: res?.reason }); + } + }) + .catch((e) => { + this._tracePush(Date.now(), "INTERACT_FAIL", { assetId, actionLabel, reason: String(e?.message || e) }); + }); + + this._plan = { type: "WAIT" }; + this._planRemaining = 0.6; + return; + } + + // === PICK_UP === + if (action === "PICK_UP") { + const assetId = String(params.assetId || ""); + console.log(`[AI PICK_UP] Attempting to pick up: assetId="${assetId}"`); + + this._tracePush(Date.now(), "PICK_UP", { assetId }); + + Promise.resolve() + .then(() => this.vlm?.pickUpAsset?.({ agent: this, assetId })) + .then((res) => { + if (res?.ok) { + this._tracePush(Date.now(), "PICK_UP_OK", { assetId }); + console.log(`[AI PICK_UP] Successfully picked up: ${assetId}`); + } else { + this._tracePush(Date.now(), "PICK_UP_FAIL", { assetId, reason: res?.reason }); + console.warn(`[AI PICK_UP] Failed: ${res?.reason}`); + } + }) + .catch((e) => { + this._tracePush(Date.now(), "PICK_UP_FAIL", { assetId, reason: String(e?.message || e) }); + }); + + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + + // === DROP === + if (action === "DROP") { + console.log(`[AI DROP] Attempting to drop held item`); + + this._tracePush(Date.now(), "DROP", {}); + + Promise.resolve() + .then(() => this.vlm?.dropAsset?.({ agent: this })) + .then((res) => { + if (res?.ok) { + this._tracePush(Date.now(), "DROP_OK", { assetId: res.assetId }); + console.log(`[AI DROP] Successfully dropped: ${res.assetId}`); + } else { + this._tracePush(Date.now(), "DROP_FAIL", { reason: res?.reason }); + console.warn(`[AI DROP] Failed: ${res?.reason}`); + } + }) + .catch((e) => { + this._tracePush(Date.now(), "DROP_FAIL", { reason: String(e?.message || e) }); + }); + + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + + // === TURN_LEFT / TURN_RIGHT === + if (action === "TURN_LEFT" || action === "TURN_RIGHT") { + const deg = clampNum(params.degrees, 15, 90, 30); + this._plan = { type: "TURN", dir: action === "TURN_LEFT" ? 1 : -1, radians: (deg * Math.PI) / 180 }; + this._planRemaining = this._plan.radians; + this._turnAcc = 0; + return; + } + + // === LOOK_UP / LOOK_DOWN / PITCH_UP / PITCH_DOWN === + if (action === "LOOK_UP" || action === "PITCH_UP") { + const deg = clampNum(params.degrees, 10, 60, 20); + this._plan = { type: "PITCH", dir: 1, radians: (deg * Math.PI) / 180 }; + this._planRemaining = this._plan.radians; + return; + } + if (action === "LOOK_DOWN" || action === "PITCH_DOWN") { + const deg = clampNum(params.degrees, 10, 60, 20); + this._plan = { type: "PITCH", dir: -1, radians: (deg * Math.PI) / 180 }; + this._planRemaining = this._plan.radians; + return; + } + + // === EDITOR: CREATE_PRIMITIVE === + if (action === "CREATE_PRIMITIVE") { + if (!this.vlm?.isEditorMode?.()) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: "not-edit-mode" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + const shape = String(params.shape || "box").toLowerCase(); + this._tracePush(Date.now(), "EDIT_CREATE_PRIMITIVE", { shape }); + Promise.resolve() + .then(() => this.vlm?.createPrimitiveInEditor?.({ agent: this, shape })) + .then((res) => { + if (!res?.ok) this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: res?.reason || "create-failed" }); + }) + .catch((e) => { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: String(e?.message || e) }); + }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.45; + return; + } + + // === EDITOR: SPAWN_LIBRARY_ASSET === + if (action === "SPAWN_LIBRARY_ASSET") { + if (!this.vlm?.isEditorMode?.()) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: "not-edit-mode" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + const assetName = String(params.assetName || "").trim(); + this._tracePush(Date.now(), "EDIT_SPAWN_LIBRARY_ASSET", { assetName }); + Promise.resolve() + .then(() => this.vlm?.spawnLibraryAssetInEditor?.({ agent: this, assetName })) + .then((res) => { + if (!res?.ok) this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: res?.reason || "spawn-failed" }); + }) + .catch((e) => { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: String(e?.message || e) }); + }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.55; + return; + } + + // === EDITOR: TRANSFORM_OBJECT === + if (action === "TRANSFORM_OBJECT") { + if (!this.vlm?.isEditorMode?.()) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: "not-edit-mode" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + const targetType = String(params.targetType || "").toLowerCase(); + const targetId = String(params.targetId || ""); + const editParams = { + targetType, + targetId, + moveX: Number(params.moveX) || 0, + moveY: Number(params.moveY) || 0, + moveZ: Number(params.moveZ) || 0, + rotateYDeg: Number(params.rotateYDeg) || 0, + scaleMul: Number(params.scaleMul) || 1, + setPositionX: Number.isFinite(Number(params.setPositionX)) ? Number(params.setPositionX) : undefined, + setPositionY: Number.isFinite(Number(params.setPositionY)) ? Number(params.setPositionY) : undefined, + setPositionZ: Number.isFinite(Number(params.setPositionZ)) ? Number(params.setPositionZ) : undefined, + setRotationYDeg: Number.isFinite(Number(params.setRotationYDeg)) ? Number(params.setRotationYDeg) : undefined, + setScaleX: Number.isFinite(Number(params.setScaleX)) ? Number(params.setScaleX) : undefined, + setScaleY: Number.isFinite(Number(params.setScaleY)) ? Number(params.setScaleY) : undefined, + setScaleZ: Number.isFinite(Number(params.setScaleZ)) ? Number(params.setScaleZ) : undefined, + snapToCrosshair: params.snapToCrosshair === true, + }; + this._tracePush(Date.now(), "EDIT_TRANSFORM_OBJECT", editParams); + Promise.resolve() + .then(() => this.vlm?.transformObjectInEditor?.({ ...editParams, agent: this })) + .then((res) => { + if (!res?.ok) this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: res?.reason || "transform-failed" }); + }) + .catch((e) => { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: String(e?.message || e) }); + }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.45; + return; + } + + // === EDITOR: GENERATE_ASSET === + if (action === "GENERATE_ASSET") { + if (!this.vlm?.isEditorMode?.()) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: "not-edit-mode" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + const prompt = String(params.prompt || "").trim(); + if (!prompt) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: "missing-prompt" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + const placeNow = params.placeNow !== false; + this._tracePush(Date.now(), "EDIT_GENERATE_ASSET", { prompt: prompt.slice(0, 120), placeNow }); + Promise.resolve() + .then(() => this.vlm?.generateAssetInEditor?.({ agent: this, prompt, placeNow })) + .then((res) => { + if (!res?.ok) { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: res?.reason || "asset-generate-failed" }); + return; + } + this._tracePush(Date.now(), "EDIT_ASSET_READY", { + action, + assetName: String(res?.assetName || ""), + assetId: String(res?.assetId || ""), + reused: !!res?.reused, + placed: !!res?.placed, + }); + const labelId = res?.assetId ? ` [id: ${res.assetId}]` : ""; + const kind = res?.reused ? "Reused" : "Generated"; + this._setThought(`${kind}: ${String(res?.assetName || "asset")}${labelId}. Next: transform to fit.`); + }) + .catch((e) => { + this._tracePush(Date.now(), "EDIT_FAIL", { action, reason: String(e?.message || e) }); + }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.8; + return; + } + + // === GOTO_LOCATION / GOTO_TAG === + if (action === "GOTO_LOCATION" || action === "GOTO_TAG") { + const locId = String(params.locationId || params.tagId || ""); + + // Special case: "start" returns to start position + if (locId === "start" && this._startPosition) { + this._plan = { type: "GOTO", x: this._startPosition.x, z: this._startPosition.z }; + this._planRemaining = 999; + this._tracePush(Date.now(), "GOTO_START", {}); + return; + } + + // Find tag by ID + const tags = this.getTags?.() || []; + const t = tags.find((x) => x?.id === locId); + if (t?.position) { + this._plan = { type: "GOTO", x: t.position.x, z: t.position.z }; + this._planRemaining = 999; + return; + } + + // Tag not found - wait briefly + this._tracePush(Date.now(), "GOTO_FAIL", { locId, reason: "not found" }); + this._plan = { type: "WAIT" }; + this._planRemaining = 0.5; + return; + } + + // === MOVEMENT === + const isMove = ["MOVE_FORWARD", "MOVE_BACKWARD", "STRAFE_LEFT", "STRAFE_RIGHT", "MOVE_UP", "MOVE_DOWN"].includes(action); + if (isMove) { + const steps = clampInt(params.steps, 1, 8, 2); + this._plan = { type: "MOVE", dir: action, steps }; + this._planRemaining = steps; + this._moveStepAcc = 0; + return; + } + + // === WAIT (explicit or fallback) === + const secs = clampNum(params.seconds, 0.3, 3, 0.5); + this._plan = { type: "WAIT" }; + this._planRemaining = secs; + } + + _stepPlan(dt) { + if (!this._plan) return true; + const p = this.body.translation(); + + if (this._plan.type === "WAIT") { + this._planRemaining -= dt; + return this._planRemaining <= 0; + } + + if (this._plan.type === "TURN") { + const turnRate = 2.4; // rad/sec + const dYaw = Math.min(this._planRemaining, turnRate * dt); + this.group.rotation.y += dYaw * this._plan.dir; + this._planRemaining -= dYaw; + this._turnAcc += dYaw; + if (this._turnAcc >= Math.PI / 6) { + this._tracePush(Date.now(), "TURN_PROGRESS", { degrees: Math.round((this._turnAcc * 180) / Math.PI) }); + this._turnAcc = 0; + } + return this._planRemaining <= 0; + } + + if (this._plan.type === "PITCH") { + const pitchRate = 2.6; // rad/sec + const d = Math.min(this._planRemaining, pitchRate * dt); + const before = this.pitch || 0; + const maxPitch = (85 * Math.PI) / 180; + let after = before + d * this._plan.dir; + after = Math.max(-maxPitch, Math.min(maxPitch, after)); + this.pitch = after; + const applied = Math.abs(after - before); + this._planRemaining -= applied; + // If we hit the clamp and couldn't apply, consider the action done. + if (applied <= 1e-6) this._planRemaining = 0; + return this._planRemaining <= 0; + } + + // Compute basis from yaw. + const yaw = this.group.rotation.y; + const forward = new THREE.Vector3(Math.sin(yaw), 0, Math.cos(yaw)); + const right = new THREE.Vector3(forward.z, 0, -forward.x); + + if (this._plan.type === "GOTO") { + const dx = this._plan.x - p.x; + const dz = this._plan.z - p.z; + const dist = Math.sqrt(dx * dx + dz * dz); + if (dist < 0.35) return true; + const vx = (dx / (dist || 1)) * this.walkSpeed; + const vz = (dz / (dist || 1)) * this.walkSpeed; + const desired = { x: vx * dt, y: -2.0 * dt, z: vz * dt }; + const m = this._computeConservativeMovement(desired); + const mx = m.x, my = m.y, mz = m.z; + this.body.setNextKinematicTranslation({ x: p.x + mx, y: p.y + my, z: p.z + mz }); + this.group.rotation.y = Math.atan2(vx, vz); + return false; + } + + if (this._plan.type === "MOVE") { + const stepMeters = Number(this.vlm.stepMeters ?? 0.35); + const dir = this._plan.dir; + let v = new THREE.Vector3(); + if (dir === "MOVE_FORWARD") v.copy(forward); + if (dir === "MOVE_BACKWARD") v.copy(forward).multiplyScalar(-1); + if (dir === "STRAFE_LEFT") v.copy(right).multiplyScalar(-1); + if (dir === "STRAFE_RIGHT") v.copy(right); + if (dir === "MOVE_UP") v.set(0, 1, 0); + if (dir === "MOVE_DOWN") v.set(0, -1, 0); + + // Consume "steps" by distance traveled (approx). + const speed = this.walkSpeed; + const distThisFrame = speed * dt; + const verticalMove = dir === "MOVE_UP" || dir === "MOVE_DOWN"; + const desired = verticalMove + ? { x: 0, y: v.y * speed * dt, z: 0 } + : { x: v.x * speed * dt, y: -2.0 * dt, z: v.z * speed * dt }; + const m = this._computeConservativeMovement(desired); + const mx = m.x, my = m.y, mz = m.z; + this.body.setNextKinematicTranslation({ x: p.x + mx, y: p.y + my, z: p.z + mz }); + + // decrement steps by fraction of stepMeters + this._moveStepAcc += distThisFrame / Math.max(0.05, stepMeters); + if (this._moveStepAcc >= 1) { + const whole = Math.floor(this._moveStepAcc); + this._moveStepAcc -= whole; + this._tracePush(Date.now(), "MOVE_PROGRESS", { steps: whole, dir }); + } + this._planRemaining -= distThisFrame / Math.max(0.05, stepMeters); + if (this._planRemaining <= 0) { + // Plan completed - step counter is incremented per VLM decision, not here + return true; + } + return false; + } + + return true; + } + + _tracePush(t, type, data) { + const msg = (() => { + if (type === "TASK_START") return `task: ${String(data?.instruction || "").slice(0, 120)}`; + if (type === "DECISION") return `${data?.action || ""}`; + if (type === "PLAN_SET") return `${data?.plan?.type || ""}`; + if (type === "PLAN_DONE") return `${data?.plan?.type || ""}`; + if (type === "MOVE_PROGRESS") return `${data?.dir || ""} +${data?.steps || 0} step`; + if (type === "TURN_PROGRESS") return `turn ~${data?.degrees || 0}°`; + if (type === "FINISH_TASK") return `finish: ${String(data?.summary || "").slice(0, 120)}`; + if (type === "EDIT_ASSET_READY") return `asset ready: ${String(data?.assetName || data?.assetId || "").slice(0, 120)}`; + return ""; + })(); + this._trace.push({ t, type, msg, data }); + if (this._trace.length > this._traceLimit) this._trace.splice(0, this._trace.length - this._traceLimit); + } + + _pickWanderTarget(p) { + // Simple random walk around current position; if world has tags, bias toward them sometimes. + const tags = this.getTags() || []; + if (tags.length > 0 && Math.random() < 0.35) { + const t = tags[(Math.random() * tags.length) | 0]; + if (t?.position) return new THREE.Vector3(t.position.x, p.y, t.position.z); + } + const r = 6 + Math.random() * 10; + const a = Math.random() * Math.PI * 2; + return new THREE.Vector3(p.x + Math.cos(a) * r, p.y, p.z + Math.sin(a) * r); + } + + _applyIdleGravity(dt) { + try { + const p = this.body.translation(); + const desired = { x: 0, y: -15.0 * dt, z: 0 }; + const m = this._computeConservativeMovement(desired); + const my = m.y; + this.body.setNextKinematicTranslation({ x: p.x, y: p.y + my, z: p.z }); + } catch {} + } + + _computeConservativeMovement(desired) { + const flags = this.RAPIER.QueryFilterFlags.EXCLUDE_SENSORS; + this.controller.computeColliderMovement(this.collider, desired, flags); + const mm = this.controller.computedMovement(); + const main = { x: mm.x, y: mm.y, z: mm.z }; + if (!this.spineCollider) return main; + + // Keep the rear fake body aligned before querying its allowed movement. + this._syncSpineCollider(); + this.controller.computeColliderMovement(this.spineCollider, desired, flags); + const ms = this.controller.computedMovement(); + const spine = { x: ms.x, y: ms.y, z: ms.z }; + + // Conservative merge: use whichever collider allows less displacement per axis. + const towardZero = (a, b) => (Math.abs(a) <= Math.abs(b) ? a : b); + return { + x: towardZero(main.x, spine.x), + y: towardZero(main.y, spine.y), + z: towardZero(main.z, spine.z), + }; + } + + _syncVisual() { + const p = this.body.translation(); + this.group.position.set(p.x, p.y, p.z); + this._syncSpineCollider(); + } + + _syncSpineCollider() { + if (!this.spineCollider) return; + const p = this.body?.translation?.(); + if (!p) return; + const yaw = this.group?.rotation?.y ?? 0; + // Keep horizontal capsule aligned with model yaw and shifted toward rear torso. + const xOff = -Math.sin(yaw) * this._spineOffsetBack; + const zOff = -Math.cos(yaw) * this._spineOffsetBack; + const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.PI / 2, yaw, 0, "YXZ")); + try { + // Prefer parent-relative APIs when available (stable for colliders attached to a rigid body). + if (typeof this.spineCollider.setTranslationWrtParent === "function") { + this.spineCollider.setTranslationWrtParent({ x: xOff, y: this._spineOffsetY, z: zOff }); + } else { + this.spineCollider.setTranslation({ x: p.x + xOff, y: p.y + this._spineOffsetY, z: p.z + zOff }); + } + if (typeof this.spineCollider.setRotationWrtParent === "function") { + this.spineCollider.setRotationWrtParent({ x: q.x, y: q.y, z: q.z, w: q.w }); + } else { + this.spineCollider.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w }); + } + } catch { + // ignore physics updates if collider is unavailable this frame + } + } + + _loadGLB() { + if (!this.avatarUrl) return; + const loader = new GLTFLoader(); + const urls = Array.isArray(this.avatarUrl) ? this.avatarUrl : [this.avatarUrl]; + const tryLoad = (index) => { + if (index >= urls.length) { + console.log(`[AiAvatar] No avatar model found, using capsule fallback.`); + return; + } + loader.load( + urls[index], + (gltf) => { this._applyGLB(gltf); }, + undefined, + () => { tryLoad(index + 1); } + ); + }; + tryLoad(0); + } + + _applyGLB(gltf) { + { + // Replace fallback with GLB + try { + this.group.remove(this.fallback); + } catch {} + // Also hide the facing indicator since the model shows direction + try { + this._facing.visible = false; + } catch {} + + this.model = gltf.scene; + + // Auto-fit the model to our agent dimensions + const bbox = new THREE.Box3().setFromObject(this.model); + const size = bbox.getSize(new THREE.Vector3()); + const center = bbox.getCenter(new THREE.Vector3()); + + // Target height: roughly capsule height (halfHeight * 2 + radius * 2) + const targetHeight = this.halfHeight * 2 + this.radius * 2; + const scaleFactor = targetHeight / (size.y || 1); + // Y-squash: the current GLB has no rig, so we can't pose it into a + // proper crouch. Compress Y instead so the robot reads as a low-slung + // quadruped rather than a tall upright one. Purely cosmetic — camera + // POV (GO2_CAMERA_HEIGHT in engine.js) is the accurate signal. + this.model.scale.set(scaleFactor, scaleFactor * 0.6, scaleFactor); + + // Re-center: the group origin is at the physics body center, + // which sits at (halfHeight + radius) above the ground. + // We need to offset the model down so its feet touch the ground. + bbox.setFromObject(this.model); + const newCenter = bbox.getCenter(new THREE.Vector3()); + const newMin = bbox.min; + this.model.position.x -= newCenter.x; + this.model.position.z -= newCenter.z; + // Offset down by the body's center height so feet are on the floor + this.model.position.y = -newMin.y - (this.halfHeight + this.radius); + // Keep the model centered on the group origin so yaw rotation pivots + // around the body's geometric center (between the legs), not the head. + + // The agent's forward convention is +Z (yaw=0 looks along +Z). + // This robot model already faces +Z, so no rotation needed. + + // Create box collider matching the scaled and positioned model dimensions + bbox.setFromObject(this.model); + const finalSize = bbox.getSize(new THREE.Vector3()); + const finalCenter = bbox.getCenter(new THREE.Vector3()); + + // Remove old box collider if it exists + if (this.boxCollider) { + this.rapierWorld.removeCollider(this.boxCollider, true); + this.boxCollider = null; + } + + // Create box collider centered on the model's actual bounding box + const boxHalfExtents = { + x: finalSize.x / 2, + y: finalSize.y / 2, + z: finalSize.z / 2 + }; + this.boxCollider = this.rapierWorld.createCollider( + this.RAPIER.ColliderDesc.cuboid(boxHalfExtents.x, boxHalfExtents.y, boxHalfExtents.z) + .setFriction(0.8) + .setTranslation(finalCenter.x, finalCenter.y, finalCenter.z), + this.body + ); + + console.log(`[AI] Created box collider: ${finalSize.x.toFixed(2)}x${finalSize.y.toFixed(2)}x${finalSize.z.toFixed(2)} at offset (${finalCenter.x.toFixed(2)}, ${finalCenter.y.toFixed(2)}, ${finalCenter.z.toFixed(2)})`); + + // Headless mode: skip visual rendering (save memory/GPU), keep collider + if (this.headless) { + console.log(`[AI] Headless mode: skipping visual model rendering`); + // Don't add model to scene, don't set up animations + // Model is loaded only to calculate box collider dimensions + this.model = null; // Release reference to free memory + return; + } + + // Normal mode: add visual model + // Real meshes receive shadows but don't cast (too expensive) + this.model.traverse((m) => { + if (m.isMesh) { + m.castShadow = false; + m.receiveShadow = true; + } + }); + + // No shadow proxy box: avoid cube-like shadow blockers around the robot. + + this.group.add(this.model); + + if (gltf.animations?.length) { + this.mixer = new THREE.AnimationMixer(this.model); + this._actions = {}; + for (const clip of gltf.animations) { + this._actions[clip.name] = this.mixer.clipAction(clip); + } + // Try common idle animation names + const idle = this._actions["idle"] || this._actions["Idle"] || this._actions["Idle_A"]; + if (idle) idle.play(); + else this.mixer.clipAction(gltf.animations[0]).play(); + } + + console.log(`[AI] Loaded robot model: ${size.x.toFixed(2)}x${size.y.toFixed(2)}x${size.z.toFixed(2)}, scale=${scaleFactor.toFixed(3)}`); + } + } + + _setThought(text) { + const ctx = this._labelCtx; + if (!ctx) return; + if (this.vlm?.showSpeechBubbleInScene === false) { + ctx.clearRect(0, 0, this._labelCanvas.width, this._labelCanvas.height); + this._labelTex.needsUpdate = true; + this._labelSprite.visible = false; + return; + } + if (!text) { + ctx.clearRect(0, 0, this._labelCanvas.width, this._labelCanvas.height); + this._labelSprite.scale.set(2.0, 1.0, 1); + this._labelTex.needsUpdate = true; + this._labelSprite.visible = false; + return; + } + + const bubbleX = 20; + const bubbleY = 20; + const bubbleW = 472; + const bubbleH = 472; + const padX = 24; + const padY = 20; + const maxTextH = bubbleH - padY * 2; + + // Keep canvas size fixed to avoid stale visual remnants from resizing. + if (this._labelCanvas.width !== 512) this._labelCanvas.width = 512; + if (this._labelCanvas.height !== 512) this._labelCanvas.height = 512; + + // Fit all text within fixed bubble by reducing font size as needed. + let fontSize = 30; + let lineHeight = 36; + let lines = []; + while (fontSize >= 12) { + ctx.font = `bold ${fontSize}px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto`; + lineHeight = Math.round(fontSize * 1.18); + lines = wrapTextLines(ctx, text, bubbleW - padX * 2); + const usedH = lines.length * lineHeight; + if (usedH <= maxTextH) break; + fontSize -= 2; + } + + ctx.clearRect(0, 0, this._labelCanvas.width, this._labelCanvas.height); + this._labelSprite.visible = true; + // bubble + ctx.fillStyle = "rgba(0,0,0,0.65)"; + ctx.strokeStyle = "rgba(255,255,255,0.25)"; + roundRect(ctx, bubbleX, bubbleY, bubbleW, bubbleH, 18); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = "rgba(255,255,255,0.92)"; + ctx.font = `bold ${fontSize}px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto`; + ctx.textBaseline = "top"; + const textX = bubbleX + padX; + let textY = bubbleY + padY; + for (const line of lines) { + ctx.fillText(line, textX, textY); + textY += lineHeight; + } + this._labelSprite.scale.set(2.0, 2.0, 1); + this._labelTex.needsUpdate = true; + } + + _extractBubbleTextFromModelOutput(parsed, raw) { + const p = parsed && typeof parsed === "object" ? parsed : {}; + const obs = + p.observation || + p.obs || + p.perception || + p.sceneObservation || + p.visualObservation || + p.params?.observation || + ""; + if (typeof obs === "string" && obs.trim()) return obs.trim(); + const action = typeof p.action === "string" ? p.action.trim() : ""; + if (action) return `Action: ${action}`; + + const rawText = typeof raw === "string" ? raw : ""; + if (rawText) { + const m = rawText.match(/"observation"\s*:\s*"([^"]+)"/i); + if (m?.[1]) return m[1]; + } + return ""; + } + + _memoryKey() { + return `sparkWorldAiMemory:${this.getWorldKey()}:${this.id}`; + } + + _loadMemory() { + try { + const raw = localStorage.getItem(this._memoryKey()); + return raw ? JSON.parse(raw) : { seenTags: {} }; + } catch { + return { seenTags: {} }; + } + } + + _saveMemory() { + try { + localStorage.setItem(this._memoryKey(), JSON.stringify(this.memory)); + } catch { + // ignore + } + } + + _rememberTag(tag) { + if (!tag?.id) return; + const now = Date.now(); + if (!this.memory.seenTags) this.memory.seenTags = {}; + const entry = this.memory.seenTags[tag.id] || { + firstSeen: now, + lastSeen: now, + count: 0, + title: tag.title || "", + notes: tag.notes || "", + }; + entry.lastSeen = now; + entry.count = (entry.count || 0) + 1; + entry.title = tag.title || entry.title; + entry.notes = tag.notes || entry.notes; + this.memory.seenTags[tag.id] = entry; + // Throttle writes a bit + if (!this._nextMemSave || now > this._nextMemSave) { + this._nextMemSave = now + 1200; + this._saveMemory(); + } + } + + _publishGlobals() { + if (typeof window === "undefined") return; + if (!window.__aiAgentPositions) window.__aiAgentPositions = {}; + const [x, y, z] = this.getPosition(); + window.__aiAgentPositions[this.id] = { x, y, z }; + } +} + +function safeParseJson(text) { + try { + return JSON.parse(text); + } catch { + // Attempt to extract the first JSON object. + const s = String(text || ""); + const i = s.indexOf("{"); + const j = s.lastIndexOf("}"); + if (i !== -1 && j !== -1 && j > i) { + try { + return JSON.parse(s.slice(i, j + 1)); + } catch {} + } + return null; + } +} + +function roundRect(ctx, x, y, w, h, r) { + const rr = Math.min(r, w / 2, h / 2); + ctx.beginPath(); + ctx.moveTo(x + rr, y); + ctx.arcTo(x + w, y, x + w, y + h, rr); + ctx.arcTo(x + w, y + h, x, y + h, rr); + ctx.arcTo(x, y + h, x, y, rr); + ctx.arcTo(x, y, x + w, y, rr); + ctx.closePath(); +} + +function wrapTextLines(ctx, text, maxWidth) { + const words = String(text).replace(/\s+/g, " ").trim().split(" "); + const lines = []; + let line = ""; + for (const rawWord of words) { + const word = rawWord || ""; + const test = line ? `${line} ${word}` : word; + if (ctx.measureText(test).width <= maxWidth || !line) { + // If single word is too long, hard-break it. + if (!line && ctx.measureText(word).width > maxWidth) { + let chunk = ""; + for (const ch of word) { + const next = chunk + ch; + if (ctx.measureText(next).width > maxWidth && chunk) { + lines.push(chunk); + chunk = ch; + } else { + chunk = next; + } + } + line = chunk; + } else { + line = test; + } + } else { + lines.push(line); + line = word; + } + } + if (line) lines.push(line); + return lines.length ? lines : [""]; +} diff --git a/misc/DimSim/src/ai/modelConfig.js b/misc/DimSim/src/ai/modelConfig.js new file mode 100644 index 0000000000..faf73b0dd7 --- /dev/null +++ b/misc/DimSim/src/ai/modelConfig.js @@ -0,0 +1,14 @@ +// Single source of truth for SimStudio model selection. +// Update these two values only. +export const MODEL_CONFIG = { + // Used by editor-mode spawned agents AND Scene Builder (vibeCreator) + editorMode: "gemini-3-flash-preview", + // Used by sim mode task agent + simMode: "gemini-robotics-er-1.5-preview", +}; + +// model: "gemini-3.1-pro-preview", +// model: "gpt-4o", +// model: "gpt-4.1-2025-04-14", // OpenAI GPT-4.1 +// model: "gemini-3-flash-preview", // Google Gemini Flash +// model: "gemini-robotics-er-1.5-preview", diff --git a/misc/DimSim/src/ai/sim/vlmActions.js b/misc/DimSim/src/ai/sim/vlmActions.js new file mode 100644 index 0000000000..7b1460b6e3 --- /dev/null +++ b/misc/DimSim/src/ai/sim/vlmActions.js @@ -0,0 +1,109 @@ +import { MODEL_CONFIG } from "../modelConfig.js"; + +// Sim-only action surface for SimStudio/DimSim parity. +// Keep this list free of editor/build actions. + +export const ACTIONS = [ + // === MOVEMENT === + { + id: "MOVE_FORWARD", + description: "Move forward. Use for approaching things you see.", + params: { steps: "integer 1-5" }, + }, + { + id: "MOVE_BACKWARD", + description: "Move backward. Use to back away or reposition.", + params: { steps: "integer 1-3" }, + }, + { + id: "STRAFE_LEFT", + description: "Sidestep left without turning.", + params: { steps: "integer 1-3" }, + }, + { + id: "STRAFE_RIGHT", + description: "Sidestep right without turning.", + params: { steps: "integer 1-3" }, + }, + { + id: "MOVE_UP", + description: "Move upward (float up) for vertical repositioning in sim.", + params: { steps: "integer 1-3" }, + }, + { + id: "MOVE_DOWN", + description: "Move downward (float down) for vertical repositioning in sim.", + params: { steps: "integer 1-3" }, + }, + + // === LOOKING/TURNING === + { + id: "TURN_LEFT", + description: "Turn body left (yaw). Use to see what's to your left or explore new directions.", + params: { degrees: "number 30-90" }, + }, + { + id: "TURN_RIGHT", + description: "Turn body right (yaw). Use to see what's to your right or explore new directions.", + params: { degrees: "number 30-90" }, + }, + { + id: "LOOK_UP", + description: "Tilt view upward. Use to see shelves, ceilings, tall objects.", + params: { degrees: "number 15-45" }, + }, + { + id: "LOOK_DOWN", + description: "Tilt view downward. Use to see floor, low objects, items on ground.", + params: { degrees: "number 15-45" }, + }, + + // === NAVIGATION === + { + id: "GOTO_LOCATION", + description: "Navigate toward a known location. Use 'start' to return to where you began.", + params: { locationId: "string (tag id from nearbyLocations, or 'start')" }, + }, + + // === INTERACTION === + { + id: "INTERACT", + description: "Interact with an object. REQUIRES: object in NEARBY OBJECTS list AND distance < 1.5m AND object should be in your field of vision. Use the EXACT assetId from [id: ...] brackets!", + params: { assetId: "string (EXACT id from [id: xxx] in NEARBY OBJECTS)", actionLabel: "string (from can: list)" }, + }, + + // === PICK UP / DROP === + { + id: "PICK_UP", + description: "Pick up a pickable object. REQUIRES: object marked [pickable] in NEARBY OBJECTS AND distance < 1.5m AND you're not already holding something AND object should be in your field of vision.", + params: { assetId: "string (EXACT id from [id: xxx] in NEARBY OBJECTS)" }, + }, + { + id: "DROP", + description: "Drop the object you're currently holding. Places it in front of you.", + params: {}, + }, + + // === META === + { + id: "THINK", + description: "Pause to reason about your situation. Use when stuck or need to reconsider your approach.", + params: { thought: "string (your reasoning)" }, + }, + { + id: "DONE", + description: "Task is complete. Only use when you've achieved the goal.", + params: { summary: "string (what you accomplished)" }, + }, +]; + +export const DEFAULTS = { + model: MODEL_CONFIG.simMode, + decideEverySteps: 6, + stepMeters: 0.4, + maxToiMeters: 50, +}; +// model: "gpt-4o", +// model: "gemini-3.1-pro-preview", +// model: "gemini-3-flash-preview", // Google Gemini Flash +// model: "gemini-robotics-er-1.5-preview", diff --git a/misc/DimSim/src/ai/sim/vlmPrompt.js b/misc/DimSim/src/ai/sim/vlmPrompt.js new file mode 100644 index 0000000000..e7725cad7e --- /dev/null +++ b/misc/DimSim/src/ai/sim/vlmPrompt.js @@ -0,0 +1,80 @@ +import { ACTIONS } from "./vlmActions.js"; + +export function buildPrompt({ actions = ACTIONS } = {}) { + const actionList = actions + .map((a) => ` ${a.id}: ${a.description}${a.params && Object.keys(a.params).length ? ` | params: ${JSON.stringify(a.params)}` : ""}`) + .join("\n"); + + return `You are an embodied AI agent navigating a 3D environment. You see through a first-person camera and receive ONE screenshot per decision. After each action completes, you'll get a NEW screenshot showing the result. + +## Your Actions +${actionList} + +## Decision Process + +For each screenshot: +1. **OBSERVE**: What do you see? Be specific about objects, their positions, and distances. +2. **THINK**: How does this relate to your goal? What should you do next? +3. **ACT**: Choose ONE action. + +## Exploration Strategy + +Since you only see one frame at a time: +- **Turn incrementally** (30-90°) to survey your surroundings +- **Move toward** interesting objects or unexplored areas you see +- **Look up/down** if you need to see shelves, floors, or tall objects +- **Remember** what you've seen in previous frames (your history is provided) + +## Task Decomposition + +Break complex tasks into steps: +EXAMPLE: +- "Find the book" → Turn to look around → Move toward bookshelf → Look at books +- "Go to kitchen" → Look for doorways → Navigate through them → Identify kitchen + +## Interaction Rules + +To interact with an object: +1. It must appear in "NEARBY OBJECTS" list +2. It must be within 1.5 meters (check the distance in parentheses) +3. It must be visible in your Field of Vision. +4. Use INTERACT with: + - assetId: the EXACT ID shown in [id: xxx] brackets - copy it exactly! + - actionLabel: one of the actions from the "can:" list + +Example: If you see "Fridge [id: 73799fa3d397c-19b5c3d31fb] (0.8m) - Closed → can: Open" +Then use: {"action": "INTERACT", "params": {"assetId": "73799fa3d397c-19b5c3d31fb", "actionLabel": "Open"}} + +## Pick Up / Drop Rules + +Some objects are marked [pickable] - you can carry them: +- Use PICK_UP with the assetId to grab it (must be within 1.5m, can only hold ONE item) +- Use DROP to place the held item in front of you +- When holding something, it shows in "HOLDING:" at the top of the context + +Example pickup: {"action": "PICK_UP", "params": {"assetId": "73799fa3d397c-19b5c3d31fb"}} +Example drop: {"action": "DROP", "params": {}} + +**CRITICAL**: +- The assetId must be EXACTLY as shown in [id: ...] - don't make up IDs! +- If no objects appear in NEARBY OBJECTS, move around to find them +- If distance > 1.5m, move closer first +- If interaction fails, try moving forward 1-2 steps and try again + +Hard constraints: +- If object IDs are missing/unclear, do NOT guess; reorient until the target appears in nearby lists. +- If IDs are missing, navigate yourself (MOVE/TURN/LOOK) until IDs are visible. +- Do not claim completion unless the final screenshot visibly matches the task intent. + +## Output Format + +Return ONLY valid JSON: +{ + "observation": "What I see in this screenshot", + "thinking": "My reasoning about what to do", + "action": "ACTION_NAME", + "params": { ... } +} + +No markdown. No extra text. JSON only.`; +} diff --git a/misc/DimSim/src/ai/visionCapture.js b/misc/DimSim/src/ai/visionCapture.js new file mode 100644 index 0000000000..63d5215d94 --- /dev/null +++ b/misc/DimSim/src/ai/visionCapture.js @@ -0,0 +1,267 @@ +import * as THREE from "three"; + +// ============================================================================= +// AGENT POV CAPTURE SYSTEM +// ============================================================================= +// +// For Gaussian splats to render correctly, the SparkRenderer needs to sort splats +// based on the camera position. This sorting happens during the render call. +// +// ARCHITECTURE: +// Instead of capturing mid-frame, we use a request/callback system: +// 1. Agent requests a capture -> we queue the request +// 2. Main render loop sees pending request +// 3. Main loop renders from AGENT's POV first (splats get sorted for agent) +// 4. Capture the result +// 5. Then render from PLAYER's POV (splats get re-sorted for player) +// +// This ensures the agent always gets a properly rendered frame. + +const _lampByAgent = new WeakMap(); + +// Pending capture requests: Map +const _pendingCaptures = new Map(); + +// Track if we have a pending capture +export function hasPendingCapture() { + return _pendingCaptures.size > 0; +} + +// Get all pending captures +export function getPendingCaptures() { + return Array.from(_pendingCaptures.values()); +} + +// Clear a pending capture after it's processed +export function clearPendingCapture(agentId) { + _pendingCaptures.delete(agentId); +} + +/** + * Request a capture - returns a promise that resolves when the capture is complete. + * The actual capture is performed by the main render loop via processPendingCaptures(). + */ +export function requestAgentCapture({ + agent, + renderer, + scene, + mainCamera, + width = 960, // Wider for more human-like FOV + height = 432, // ~2.2:1 aspect ratio (wider than 16:9) + eyeHeight = 0.55, + jpegQuality = 0.75, + fov = 80, // Wider vertical FOV for more peripheral vision + near = 0.05, + far = 2000, + headLamp = null, + preRender = null, + renderFn = null, // optional: (renderer, scene, camera) => void — renders active view mode +}) { + return new Promise((resolve, reject) => { + if (!agent) { + reject(new Error("No agent provided")); + return; + } + + const agentId = agent.id || "default"; + + // Store the capture request + _pendingCaptures.set(agentId, { + agent, + renderer, + scene, + mainCamera, + width, + height, + eyeHeight, + jpegQuality, + fov, + near, + far, + headLamp, + preRender, + renderFn, + resolve, + reject, + }); + }); +} + +/** + * Process all pending captures - called from the main render loop. + * This renders from each agent's POV and captures the result. + * + * We do a "warm-up" render first to trigger splat sorting, wait for the + * renderer to complete, then do the actual capture render. + */ +export async function processPendingCaptures() { + const captures = getPendingCaptures(); + if (captures.length === 0) return; + + for (const capture of captures) { + try { + const base64 = await performCaptureWithDelay(capture); + capture.resolve(base64); + } catch (e) { + capture.reject(e); + } finally { + clearPendingCapture(capture.agent.id || "default"); + } + } +} + +/** + * Perform capture with a delay to allow SparkRenderer to sort splats. + * 1. First render triggers splat sorting for agent's viewpoint + * 2. Wait 500ms for GPU sorting to complete + * 3. Second render captures with properly sorted splats + */ +async function performCaptureWithDelay(captureParams) { + const { + agent, + renderer, + scene, + mainCamera, + width, + height, + eyeHeight, + fov, + near, + far, + headLamp, + preRender, + jpegQuality, + } = captureParams; + + if (!agent || !renderer || !scene || !mainCamera) return null; + + let lamp = null; + + // Calculate agent's eye position and direction + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch); + const sp = Math.sin(pitch); + const forward = new THREE.Vector3(Math.sin(yaw) * cp, sp, Math.cos(yaw) * cp); + const eyeY = ay + eyeHeight; + + // Use a dedicated offscreen camera so user view never switches. + const captureCamera = new THREE.PerspectiveCamera(fov, width / height, near, far); + captureCamera.position.set(ax, eyeY, az); + captureCamera.lookAt(ax + forward.x, eyeY + forward.y, az + forward.z); + captureCamera.updateProjectionMatrix(); + captureCamera.updateMatrixWorld(true); + + const prevTarget = renderer.getRenderTarget?.() || null; + const captureTarget = new THREE.WebGLRenderTarget(width, height, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, + depthBuffer: true, + stencilBuffer: false, + }); + + // Optional capture-only fill light. Keep null for strict view parity. + if (headLamp && typeof headLamp === "object") { + lamp = _lampByAgent.get(agent); + if (!lamp) { + lamp = new THREE.PointLight(0xffffff, headLamp.intensity, headLamp.distance, headLamp.decay); + _lampByAgent.set(agent, lamp); + } + const fwdN = forward.clone().normalize(); + const up = new THREE.Vector3(0, 1, 0); + const off = headLamp.offset || { x: 0, y: 1.0, z: 0.6 }; + const right = new THREE.Vector3().crossVectors(fwdN, up).normalize(); + const lampPos = new THREE.Vector3(ax, eyeY, az) + .addScaledVector(right, off.x) + .addScaledVector(up, off.y) + .addScaledVector(fwdN, off.z); + lamp.position.copy(lampPos); + scene.add(lamp); + } + + // Call preRender callback + let cleanup = null; + if (typeof preRender === "function") { + try { + cleanup = preRender(captureCamera) || null; + } catch { + // ignore + } + } + + const { renderFn } = captureParams; + const doRender = () => { + renderer.setRenderTarget(captureTarget); + if (typeof renderFn === "function") { + // Intentionally pass captureCamera; renderFn should avoid mutating player camera. + renderFn(renderer, scene, captureCamera); + } else { + renderer.render(scene, captureCamera); + } + renderer.setRenderTarget(null); + }; + + // FIRST RENDER: Trigger splat sorting for agent's viewpoint + doRender(); + + // Give Spark sorting a short moment to settle. + await new Promise(resolve => setTimeout(resolve, 180)); + + // SECOND RENDER: Capture with properly sorted splats + doRender(); + + // Read pixels from offscreen target and encode JPEG. + const raw = new Uint8Array(width * height * 4); + renderer.readRenderTargetPixels(captureTarget, 0, 0, width, height, raw); + const flipped = new Uint8ClampedArray(width * height * 4); + const rowBytes = width * 4; + for (let y = 0; y < height; y++) { + const srcY = height - 1 - y; + const srcOff = srcY * rowBytes; + const dstOff = y * rowBytes; + // Match on-screen output transform: convert linear RT pixels to sRGB. + for (let i = 0; i < rowBytes; i += 4) { + const r = raw[srcOff + i + 0] / 255; + const g = raw[srcOff + i + 1] / 255; + const b = raw[srcOff + i + 2] / 255; + const a = raw[srcOff + i + 3]; + const toSrgb = (x) => (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); + flipped[dstOff + i + 0] = Math.max(0, Math.min(255, Math.round(toSrgb(r) * 255))); + flipped[dstOff + i + 1] = Math.max(0, Math.min(255, Math.round(toSrgb(g) * 255))); + flipped[dstOff + i + 2] = Math.max(0, Math.min(255, Math.round(toSrgb(b) * 255))); + flipped[dstOff + i + 3] = a; + } + } + const cvs = document.createElement("canvas"); + cvs.width = width; + cvs.height = height; + const ctx = cvs.getContext("2d"); + if (!ctx) return null; + ctx.putImageData(new ImageData(flipped, width, height), 0, 0); + const dataUrl = cvs.toDataURL("image/jpeg", jpegQuality); + + // Remove optional capture lamp + if (lamp) scene.remove(lamp); + + // Call cleanup + if (typeof cleanup === "function") { + try { + cleanup(); + } catch { + // ignore + } + } + + renderer.setRenderTarget(prevTarget); + captureTarget.dispose(); + + const idx = dataUrl.indexOf("base64,"); + return idx !== -1 ? dataUrl.slice(idx + "base64,".length) : null; +} + +// Legacy function for backwards compatibility - now just calls requestAgentCapture +export async function captureAgentPovBase64(params) { + return requestAgentCapture(params); +} diff --git a/misc/DimSim/src/ai/vlmClient.js b/misc/DimSim/src/ai/vlmClient.js new file mode 100644 index 0000000000..84b5ee0bbe --- /dev/null +++ b/misc/DimSim/src/ai/vlmClient.js @@ -0,0 +1,36 @@ +// Browser client: talks to the SimStudio VLM backend server +// so your OpenAI key never ships to the browser. + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function requestVlmDecision({ endpoint, model, prompt, imageBase64, context, messages }) { + const payload = JSON.stringify({ model, prompt, imageBase64, context, messages }); + const maxAttempts = 6; + let lastError = null; + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + const res = await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload, + }); + if (res.ok) return await res.json(); + + const text = await res.text().catch(() => ""); + const isRetryable = res.status === 429 || res.status === 503 || res.status === 504; + if (!isRetryable || attempt >= maxAttempts) { + throw new Error(`VLM request failed (${res.status}): ${text || res.statusText}`); + } + + lastError = new Error(`retryable status ${res.status}`); + const retryAfter = Number(res.headers.get("retry-after") || 0); + const retryAfterMs = retryAfter > 0 ? retryAfter * 1000 : 0; + const backoffMs = Math.min(12000, 700 * 2 ** (attempt - 1)); + const jitterMs = Math.floor(Math.random() * 350); + await sleep(Math.max(retryAfterMs, backoffMs) + jitterMs); + } + + throw lastError || new Error("VLM request failed"); +} diff --git a/misc/DimSim/src/dimos/dimosBridge.ts b/misc/DimSim/src/dimos/dimosBridge.ts new file mode 100644 index 0000000000..1b5a4ada8b --- /dev/null +++ b/misc/DimSim/src/dimos/dimosBridge.ts @@ -0,0 +1,557 @@ +/** + * DimosBridge — Browser-side WebSocket client for dimos integration. + * + * Uses separate WebSocket connections so large sensor streams do not block + * each other or real-time odom/cmd_vel: + * wsControl → /odom, /cmd_vel (tiny packets, real-time) + * wsSensors → /lidar + snapshots + * wsRgb → /color_image + * wsDepth → /depth_image + * + * All messages are LCM-encoded binary packets using @dimos/msgs, sent over + * WebSocket to the bridge server which relays them to/from dimos via LCM/UDP. + */ + +// @ts-ignore — CDN import (runs in browser, no Deno/Node type resolution) +import { + encodePacket, + decodePacket, + geometry_msgs, + sensor_msgs, + std_msgs, +} from "https://esm.sh/jsr/@dimos/msgs@0.1.4"; + +// -- Channels ---------------------------------------------------------------- +const CH_CMD_VEL = "/cmd_vel#geometry_msgs.Twist"; +const CH_ODOM = "/odom#geometry_msgs.PoseStamped"; +const CH_IMAGE = "/color_image#sensor_msgs.Image"; +const CH_DEPTH = "/depth_image#sensor_msgs.Image"; +const CH_LIDAR = "/lidar#sensor_msgs.PointCloud2"; + +// -- Default publish rates (ms) ---------------------------------------------- +const DEFAULT_RATES: PublishRates = { odom: 20, lidar: 200, images: 200 }; // 50 Hz odom, 5 Hz lidar, 5 Hz images +const CMD_VEL_TIMEOUT_MS = 500; +const BRIDGE_DEBUG = false; +const LIDAR_POINT_STEP = 16; + +// -- Types -------------------------------------------------------------------- + +export interface PublishRates { odom: number; lidar: number; images: number; } +export interface SensorEnable { depth: boolean; } + +export interface RgbFrame { + data: Uint8Array; + width: number; + height: number; +} + +export interface DepthFrame { + data: Float32Array; + width: number; + height: number; +} + +export interface LidarFrame { + numPoints: number; + points: Float32Array; // N*3 interleaved XYZ + intensity?: Float32Array; // N +} + +export interface OdomPose { + x: number; y: number; z: number; + qx: number; qy: number; qz: number; qw: number; +} + +export interface SensorSources { + captureRgb: () => RgbFrame | null; + captureDepth: () => DepthFrame | null; + captureLidar: () => LidarFrame | null; + getOdomPose: () => OdomPose | null; +} + +export type FrameTransform = "identity" | "ros"; + +export interface DimosBridgeOptions { + wsUrl?: string; + agent: any; + sensorSources: SensorSources; + rates?: Partial; + sensorEnable?: Partial; + frameTransform?: FrameTransform; +} + +export class DimosBridge { + wsUrl: string; + agent: any; + sensors: SensorSources; + rates: PublishRates; + sensorEnable: SensorEnable; + frameTransform: FrameTransform; + + // Separate sockets so depth backlog does not starve RGB. + wsControl: WebSocket | null; // odom + cmd_vel (tiny, real-time) + wsSensors: WebSocket | null; // lidar + snapshots + wsRgb: WebSocket | null; // color image + wsDepth: WebSocket | null; // depth image + + // Keep legacy .ws alias pointing to control for compatibility + get ws(): WebSocket | null { return this.wsControl; } + + _timers: Record>; + _dirty: { odom: boolean; lidar: boolean; images: boolean }; + _rafId: number | null; + _connected: boolean; + + _cmdVel: { linX: number; linY: number; linZ: number; angX: number; angY: number; angZ: number } | null; + _cmdVelStamp: number; + _serverLidar: boolean; + _lidarBuf: ArrayBuffer; + _lidarView: DataView; + _lidarCapacityPoints: number; + _pc2Fields: any[]; + + constructor({ wsUrl, agent, sensorSources, rates, sensorEnable, frameTransform }: DimosBridgeOptions) { + const protocol = location.protocol === "https:" ? "wss:" : "ws:"; + this.wsUrl = wsUrl || `${protocol}//${location.host}`; + this.agent = agent; + this.sensors = sensorSources; + this.rates = { ...DEFAULT_RATES, ...rates }; + this.sensorEnable = { depth: true, ...sensorEnable }; + this.frameTransform = frameTransform || "ros"; + this.wsControl = null; + this.wsSensors = null; + this.wsRgb = null; + this.wsDepth = null; + this._timers = {}; + this._dirty = { odom: false, lidar: false, images: false }; + this._rafId = null; + this._connected = false; + this._cmdVel = null; + this._cmdVelStamp = 0; + this._serverLidar = false; + this._lidarBuf = new ArrayBuffer(0); + this._lidarView = new DataView(this._lidarBuf); + this._lidarCapacityPoints = 0; + this._pc2Fields = [ + new sensor_msgs.PointField({ name: "x", offset: 0, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "y", offset: 4, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "z", offset: 8, datatype: 7, count: 1 }), + new sensor_msgs.PointField({ name: "intensity", offset: 12, datatype: 7, count: 1 }), + ]; + } + + connect(): void { + // Read channel from URL param (for multi-page parallel evals) + const channel = new URLSearchParams(location.search).get("channel") || ""; + const channelSuffix = channel ? `&channel=${channel}` : ""; + + // Control socket: odom out, cmd_vel in + this.wsControl = new WebSocket(this.wsUrl + "?ch=control" + channelSuffix); + this.wsControl.binaryType = "arraybuffer"; + + this.wsControl.onopen = () => { + console.log("[DimosBridge] control WS connected"); + this._connected = true; + this._startPublishing(); + }; + + this.wsControl.onmessage = (event: MessageEvent) => { + // Text messages: server-side physics pose updates + embodiment config + if (typeof event.data === "string") { + try { + const msg = JSON.parse(event.data); + if (msg.type === "pose") { + this._handleServerPose(msg.x, msg.y, msg.z, msg.yaw); + } else if (msg.type === "embodimentConfig") { + this._handleEmbodimentConfig(msg); + } + } catch {} + return; + } + // Binary messages: LCM packets (cmd_vel relay) + if (!(event.data instanceof ArrayBuffer)) return; + try { + const raw = new Uint8Array(event.data); + const { channel, data } = decodePacket(raw); + this._handlePacket(channel, data); + } catch {} + }; + + this.wsControl.onclose = () => { + console.log("[DimosBridge] control WS disconnected, reconnecting in 2s..."); + this._connected = false; + this._stopPublishing(); + setTimeout(() => this.connect(), 2000); + }; + + this.wsControl.onerror = () => {}; + + // Sensor socket: lidar + snapshots out (no incoming expected) + this.wsSensors = new WebSocket(this.wsUrl + "?ch=sensors" + channelSuffix); + this.wsSensors.binaryType = "arraybuffer"; + + this.wsSensors.onopen = () => { + console.log("[DimosBridge] sensor WS connected"); + }; + + this.wsSensors.onclose = () => { + console.log("[DimosBridge] sensor WS disconnected"); + }; + + this.wsSensors.onerror = () => {}; + + this.wsRgb = new WebSocket(this.wsUrl + "?ch=rgb" + channelSuffix); + this.wsRgb.binaryType = "arraybuffer"; + this.wsRgb.onclose = () => { + console.log("[DimosBridge] RGB WS disconnected"); + }; + this.wsRgb.onerror = () => {}; + + this.wsDepth = new WebSocket(this.wsUrl + "?ch=depth" + channelSuffix); + this.wsDepth.binaryType = "arraybuffer"; + this.wsDepth.onclose = () => { + console.log("[DimosBridge] depth WS disconnected"); + }; + this.wsDepth.onerror = () => {}; + } + + // -- Incoming packets ------------------------------------------------------- + + _handlePacket(channel: string, data: any): void { + if (channel === CH_CMD_VEL) { + this._handleCmdVel(data); + } + } + + _handleCmdVel(twist: any): void { + const lin = twist.linear; + const ang = twist.angular; + + let linX: number, linY: number, linZ: number; + let angX: number, angY: number, angZ: number; + + if (this.frameTransform === "ros") { + // ROS → Three.js: inverse of the cyclic permutation (x→y, y→z, z→x) + linX = lin.y; + linY = lin.z; + linZ = lin.x; + angX = ang.y; + angY = ang.z; + angZ = ang.x; + } else { + linX = lin.x; linY = lin.y; linZ = lin.z; + angX = ang.x; angY = ang.y; angZ = ang.z; + } + + this._cmdVel = { linX, linY, linZ, angX, angY, angZ }; + this._cmdVelStamp = Date.now(); + } + + /** Get current velocity, auto-zeroing after CMD_VEL_TIMEOUT_MS (safety stop). */ + getCmdVel(): { linX: number; linY: number; linZ: number; angX: number; angY: number; angZ: number } { + if (!this._cmdVel || Date.now() - this._cmdVelStamp > CMD_VEL_TIMEOUT_MS) { + return { linX: 0, linY: 0, linZ: 0, angX: 0, angY: 0, angZ: 0 }; + } + return this._cmdVel; + } + + /** Handle server-side physics pose update (Three.js Y-up frame). */ + _handleServerPose(x: number, y: number, z: number, yaw: number): void { + if (!this.agent) return; + // Move the agent body to the server-authoritative position + if (this.agent.body) { + this.agent.body.setNextKinematicTranslation({ x, y, z }); + } + if (this.agent.group) { + this.agent.group.rotation.y = yaw; + } + // Update engine's _dimosYaw for sensor capture / odom pose reading + if ((window as any).__dimosSetYaw) { + (window as any).__dimosSetYaw(yaw); + } + // Store for odom/sensor capture + this._serverPose = { x, y, z, yaw }; + } + + _serverPose: { x: number; y: number; z: number; yaw: number } | null = null; + + _handleEmbodimentConfig(msg: any): void { + console.log("[DimosBridge] embodiment config received:", msg.embodimentType || "quadruped"); + // Apply config to engine globals (dimensions, speed, type) + if ((window as any).applyEmbodiment) { + (window as any).applyEmbodiment(msg); + } + // Swap the agent's avatar model if avatarUrl changed + if (this.agent && msg.avatarUrl) { + const urls = Array.isArray(msg.avatarUrl) ? msg.avatarUrl : [msg.avatarUrl]; + this.agent.avatarUrl = urls; + // Update dimensions on the agent so _applyGLB auto-fits correctly + if (msg.radius != null) this.agent.radius = msg.radius; + if (msg.halfHeight != null) this.agent.halfHeight = msg.halfHeight; + // Remove current model and reload + if (this.agent.model) { + this.agent.group.remove(this.agent.model); + this.agent.model = null; + } + this.agent._loadGLB(); + } + } + + // -- Outgoing sensor data --------------------------------------------------- + + sceneReady = false; + + _startPublishing(): void { + // No lidar timer — server-side lidar handles it via LCM directly. + // Images default 5 Hz (configurable via rates.images) + if (this.rates.images > 0) { + this._timers["images"] = setInterval(() => this._publishImages(), this.rates.images); + } + } + + _makeHeader(frameId: string): any { + const now = Date.now(); + return new std_msgs.Header({ + stamp: new std_msgs.Time({ sec: Math.floor(now / 1000), nsec: (now % 1000) * 1_000_000 }), + frame_id: frameId, + }); + } + + _publishOdom(): void { + if (!this._isControlSocketOpen()) return; + this._publishOdomSync(this._makeHeader("world")); + } + + _publishLidar(): void { + if (!this._isSocketOpen(this.wsSensors)) return; + this._publishLidarSync(this._makeHeader("world")); + } + + _publishImages(): void { + if (!this._isSocketOpen(this.wsRgb) && !this._isSocketOpen(this.wsDepth)) return; + const camHeader = this._makeHeader("camera_optical"); + if (this._isSocketOpen(this.wsRgb)) this._publishRgbSync(camHeader); + if (this.sensorEnable.depth && this._isSocketOpen(this.wsDepth)) this._publishDepthSync(camHeader); + } + + // -- Odom ------------------------------------------------------------------- + + _odomDbgN = 0; + + _publishOdomSync(header: any): void { + try { + const pose = this.sensors.getOdomPose(); + if (!pose) return; + + this._odomDbgN++; + + // Three.js (Y-up) → ROS (Z-up) cyclic permutation: x→y, y→z, z→x + const rosQx = pose.qz; + const rosQy = pose.qx; + const rosQz = pose.qy; + const rosQw = pose.qw; + + const q = new geometry_msgs.Quaternion(); + q.x = rosQx; q.y = rosQy; q.z = rosQz; q.w = rosQw; + const pt = new geometry_msgs.Point(); + pt.x = pose.z; pt.y = pose.x; pt.z = pose.y; + const p = new geometry_msgs.Pose(); + p.position = pt; + p.orientation = q; + + header.seq = this._odomDbgN; + const odomMsg = new geometry_msgs.PoseStamped(); + odomMsg.header = header; + odomMsg.pose = p; + + if (BRIDGE_DEBUG && (this._odomDbgN <= 3 || this._odomDbgN % 100 === 0)) { + console.log(`[odom TX seq=${this._odomDbgN}] qz=${rosQz.toFixed(4)} qw=${rosQw.toFixed(4)}`); + } + + // Send on CONTROL socket (not sensor socket) + this._sendControl(CH_ODOM, odomMsg); + } catch (e) { + console.warn("[DimosBridge] odom publish error:", e); + } + } + + _stopPublishing(): void { + for (const k of Object.keys(this._timers)) clearInterval(this._timers[k]); + this._timers = {}; + if (this._rafId) cancelAnimationFrame(this._rafId); + this._rafId = null; + } + + /** Send on the control WebSocket (odom, small real-time data) */ + _sendControl(channel: string, msg: any): void { + const ws = this.wsControl; + if (!ws || ws.readyState !== WebSocket.OPEN) return; + ws.send(encodePacket(channel, msg)); + } + + /** Send on a sensor WebSocket (images, lidar — large data). */ + _sendSensor(ws: WebSocket | null, channel: string, msg: any): void { + if (!ws || ws.readyState !== WebSocket.OPEN) return; + ws.send(encodePacket(channel, msg)); + } + + /** Legacy _send — routes to appropriate socket based on channel */ + _send(channel: string, msg: any): void { + if (channel === CH_ODOM) { + this._sendControl(channel, msg); + } else if (channel === CH_IMAGE) { + this._sendSensor(this.wsRgb, channel, msg); + } else if (channel === CH_DEPTH) { + this._sendSensor(this.wsDepth, channel, msg); + } else { + this._sendSensor(this.wsSensors, channel, msg); + } + } + + // -- RGB -------------------------------------------------------------------- + + _publishRgbSync(header: any): void { + try { + if (!this._isSocketOpen(this.wsRgb)) return; + const frame = this.sensors.captureRgb(); + if (!frame) return; + + this._sendSensor(this.wsRgb, CH_IMAGE, new sensor_msgs.Image({ + header, + height: frame.height, + width: frame.width, + encoding: "jpeg", + is_bigendian: 0, + step: 0, // not applicable for compressed format + data_length: frame.data.length, + data: frame.data, + })); + } catch (e) { + console.warn("[DimosBridge] RGB publish error:", e); + } + } + + // -- Depth ------------------------------------------------------------------ + + _depthU16: Uint16Array | null = null; + + _publishDepthSync(header: any): void { + try { + if (!this._isSocketOpen(this.wsDepth)) return; + const frame = this.sensors.captureDepth(); + if (!frame) return; + + // Quantize float32 meters → uint16 millimeters (0–65.535m range, 1mm precision) + const n = frame.data.length; + if (!this._depthU16 || this._depthU16.length !== n) { + this._depthU16 = new Uint16Array(n); + } + const f32 = frame.data; + const u16 = this._depthU16; + for (let i = 0; i < n; i++) { + const mm = f32[i] * 1000; + u16[i] = mm > 65535 ? 65535 : mm < 0 ? 0 : mm; + } + const depthBytes = new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength); + + this._sendSensor(this.wsDepth, CH_DEPTH, new sensor_msgs.Image({ + header, + height: frame.height, + width: frame.width, + encoding: "16UC1", + is_bigendian: 0, + step: frame.width * 2, + data_length: depthBytes.length, + data: depthBytes, + })); + } catch (e) { + console.warn("[DimosBridge] depth publish error:", e); + } + } + + // -- LiDAR ------------------------------------------------------------------ + + _lidarDbgN = 0; + _publishLidarSync(header: any): void { + try { + if (!this._isSocketOpen(this.wsSensors)) return; + const frame = this.sensors.captureLidar(); + this._lidarDbgN++; + if (BRIDGE_DEBUG && (this._lidarDbgN <= 3 || this._lidarDbgN % 100 === 0)) { + console.log(`[DimosBridge] lidar #${this._lidarDbgN}: ${frame ? frame.numPoints : 'null'} pts, sensorWS=${this.wsSensors?.readyState}`); + } + if (!frame) return; + + const numPoints = frame.numPoints || 0; + if (numPoints === 0) return; + + this._ensureLidarCapacity(numPoints); + const pointStep = LIDAR_POINT_STEP; + const view = this._lidarView; + const pts = frame.points; + const intensity = frame.intensity; + + // Points are Three.js world-frame (Y-up). + // Convert to ROS world-frame (Z-up): cyclic permutation x→y, y→z, z→x + for (let i = 0; i < numPoints; i++) { + const off = i * pointStep; + const tx = pts[i * 3 + 0], ty = pts[i * 3 + 1], tz = pts[i * 3 + 2]; + view.setFloat32(off, tz, true); // ROS x = Three.js z + view.setFloat32(off + 4, tx, true); // ROS y = Three.js x + view.setFloat32(off + 8, ty, true); // ROS z = Three.js y + view.setFloat32(off + 12, intensity ? intensity[i] : 1.0, true); + } + + this._sendSensor(this.wsSensors, CH_LIDAR, new sensor_msgs.PointCloud2({ + header, + height: 1, + width: numPoints, + fields_length: this._pc2Fields.length, + fields: this._pc2Fields, + is_bigendian: 0, + point_step: pointStep, + row_step: numPoints * pointStep, + data_length: numPoints * pointStep, + data: new Uint8Array(this._lidarBuf, 0, numPoints * pointStep), + is_dense: 1, + })); + } catch (e) { + console.warn("[DimosBridge] LiDAR publish error:", e); + } + } + + /** Send a JSON command on the control WebSocket (used by EvalHarness). */ + sendCommand(cmd: Record): void { + const ws = this.wsControl; + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(cmd)); + } + } + + dispose(): void { + this._stopPublishing(); + if (this.wsControl) { this.wsControl.onclose = null; this.wsControl.close(); } + if (this.wsSensors) { this.wsSensors.onclose = null; this.wsSensors.close(); } + if (this.wsRgb) { this.wsRgb.onclose = null; this.wsRgb.close(); } + if (this.wsDepth) { this.wsDepth.onclose = null; this.wsDepth.close(); } + this.wsControl = null; + this.wsSensors = null; + this.wsRgb = null; + this.wsDepth = null; + } + + _isControlSocketOpen(): boolean { + return !!this.wsControl && this.wsControl.readyState === WebSocket.OPEN; + } + + _isSocketOpen(ws: WebSocket | null): boolean { + return !!ws && ws.readyState === WebSocket.OPEN; + } + + _ensureLidarCapacity(numPoints: number): void { + if (numPoints <= this._lidarCapacityPoints) return; + this._lidarCapacityPoints = numPoints; + this._lidarBuf = new ArrayBuffer(numPoints * LIDAR_POINT_STEP); + this._lidarView = new DataView(this._lidarBuf); + } +} diff --git a/misc/DimSim/src/dimos/evalHarness.ts b/misc/DimSim/src/dimos/evalHarness.ts new file mode 100644 index 0000000000..a77f6d9248 --- /dev/null +++ b/misc/DimSim/src/dimos/evalHarness.ts @@ -0,0 +1,227 @@ +/** + * EvalHarness — Browser-side eval orchestrator. + * + * Receives commands from the Deno eval runner (via WebSocket text messages), + * runs eval workflow, scores objectDistance rubric at timeout, returns result. + */ + +import { + scoreObjectDistance, + scoreRadiusContains, + type SceneState, + type ObjectDistanceCriteria, + type RadiusContainsCriteria, +} from "./rubrics.ts"; +import type { DimosBridge } from "./dimosBridge.ts"; + +export interface AgentPose { x: number; y: number; z: number; yaw: number; pitch: number; } +export interface StartPose { x?: number; y?: number; z?: number; yaw?: number; } + +export interface SuccessCriteria { + objectDistance?: ObjectDistanceCriteria; + radiusContains?: RadiusContainsCriteria; +} + +export interface Workflow { + name: string; + task: string; + environment?: string; + startPose?: StartPose; + timeoutSec?: number; + successCriteria?: SuccessCriteria; +} + +export interface EvalHarnessOptions { + bridge: DimosBridge; + getSceneState: () => SceneState; + getAgentPose: () => AgentPose | null; + channel?: string; +} + +declare global { + interface Window { __dimosAgent?: any; } +} + +export class EvalHarness { + bridge: DimosBridge; + getSceneState: () => SceneState; + getAgentPose: () => AgentPose | null; + channel: string; + + _workflow: Workflow | null = null; + _startTime = 0; + _timeoutTimer: ReturnType | null = null; + _overlay: HTMLDivElement | null = null; + + constructor({ bridge, getSceneState, getAgentPose, channel }: EvalHarnessOptions) { + this.bridge = bridge; + this.getSceneState = getSceneState; + this.getAgentPose = getAgentPose; + this.channel = channel || ""; + this._hookBridgeMessages(); + } + + _hookBridgeMessages(): void { + const origConnect = this.bridge.connect.bind(this.bridge); + this.bridge.connect = () => { + origConnect(); + setTimeout(() => { + const ws = this.bridge.ws; + if (ws) this._patchWsOnMessage(ws); + }, 100); + }; + const ws = this.bridge.ws; + if (ws) this._patchWsOnMessage(ws); + } + + _patchWsOnMessage(ws: WebSocket): void { + const origOnMessage = ws.onmessage; + const evalTypes = new Set(["startWorkflow", "stopWorkflow", "loadEnv", "ping"]); + ws.onmessage = (event: MessageEvent) => { + if (typeof event.data === "string") { + try { + const cmd = JSON.parse(event.data); + if (cmd.type && evalTypes.has(cmd.type)) { + this._handleCommand(cmd); + return; + } + } catch { /* not JSON, pass through */ } + // Pass all non-eval text messages through to origOnMessage + if (origOnMessage) (origOnMessage as (e: MessageEvent) => void).call(ws, event); + return; + } + if (origOnMessage) (origOnMessage as (e: MessageEvent) => void).call(ws, event); + }; + } + + _send(cmd: Record): void { + // Tag outgoing messages with channel for multi-page routing + if (this.channel) cmd.channel = this.channel; + this.bridge.sendCommand(cmd); + } + + async _handleCommand(cmd: { type: string; channel?: string; workflow?: Workflow; [k: string]: any }): Promise { + // Channel filtering: if cmd has a channel and it doesn't match ours, ignore + if (this.channel && cmd.channel && cmd.channel !== this.channel) return; + console.log("[eval] command:", cmd.type); + switch (cmd.type) { + case "startWorkflow": + await this._startWorkflow(cmd.workflow!); + break; + case "stopWorkflow": + await this._stopWorkflow("runner-requested"); + break; + case "loadEnv": + // Scene is already loaded in --connect mode, just ack + this._send({ type: "envReady", scene: cmd.scene }); + break; + case "ping": + this._send({ type: "pong", ts: Date.now() }); + break; + default: + break; + } + } + + async _startWorkflow(workflow: Workflow): Promise { + this._workflow = workflow; + this._startTime = Date.now(); + + console.log(`[eval] starting: ${workflow.name} — "${workflow.task}"`); + + if (workflow.startPose) { + const p = workflow.startPose; + const agent = window.__dimosAgent; + if (agent) { + agent.setPosition(p.x ?? 0, p.y ?? 0.5, p.z ?? 0); + if (p.yaw !== undefined) agent.group.rotation.y = (p.yaw * Math.PI) / 180; + } + } + + const timeoutMs = (workflow.timeoutSec || 120) * 1000; + this._timeoutTimer = setTimeout(() => this._stopWorkflow("timeout"), timeoutMs); + + this._showOverlay(workflow.task, workflow.timeoutSec || 120); + this._send({ type: "workflowStarted", name: workflow.name }); + } + + async _stopWorkflow(reason: string): Promise { + if (!this._workflow) return; + if (this._timeoutTimer) clearTimeout(this._timeoutTimer); + this._timeoutTimer = null; + + console.log(`[eval] stopped: ${this._workflow.name} (${reason})`); + + const sceneState = this.getSceneState(); + const agentPose = this.getAgentPose(); + if (agentPose) { + sceneState.agentPos = { x: agentPose.x, y: agentPose.y, z: agentPose.z }; + } + + const criteria = this._workflow.successCriteria || {}; + const scores: Record = {}; + if (criteria.objectDistance) { + scores.objectDistance = scoreObjectDistance(criteria.objectDistance, sceneState); + } + if (criteria.radiusContains) { + scores.radiusContains = scoreRadiusContains(criteria.radiusContains, sceneState); + } + + const pass = Object.values(scores).every((s: any) => s.pass !== false); + const od = scores.objectDistance; + this._showResult(pass, od ? od.details : reason); + + const result = { + type: "workflowComplete", + name: this._workflow.name, + environment: this._workflow.environment, + reason, + durationMs: Date.now() - this._startTime, + rubricScores: scores, + }; + console.log("[eval] result:", result); + this._send(result); + this._workflow = null; + } + + // -- UI overlay -------------------------------------------------------------- + + _showOverlay(task: string, timeoutSec: number): void { + if (this._overlay) this._overlay.remove(); + const el = document.createElement("div"); + el.style.cssText = "position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:99999;background:rgba(0,0,0,0.85);color:#fff;font:14px/1.5 monospace;padding:12px 24px;border-radius:10px;text-align:center;pointer-events:none;"; + const taskEl = document.createElement("div"); + taskEl.style.cssText = "color:#4fc3f7;font-size:16px;font-weight:bold;margin-bottom:4px;"; + taskEl.textContent = `EVAL: ${task}`; + const timerEl = document.createElement("div"); + timerEl.style.cssText = "color:#aaa;font-size:13px;"; + el.appendChild(taskEl); + el.appendChild(timerEl); + document.body.appendChild(el); + this._overlay = el; + + let remaining = timeoutSec; + timerEl.textContent = `${remaining}s remaining`; + const interval = setInterval(() => { + remaining--; + if (remaining <= 0 || !this._workflow) { clearInterval(interval); return; } + timerEl.textContent = `${remaining}s remaining`; + }, 1000); + } + + _showResult(pass: boolean, details: string): void { + if (this._overlay) this._overlay.remove(); + const el = document.createElement("div"); + const bg = pass ? "rgba(46,125,50,0.9)" : "rgba(198,40,40,0.9)"; + el.style.cssText = `position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:99999;background:${bg};color:#fff;font:14px/1.5 monospace;padding:12px 24px;border-radius:10px;text-align:center;pointer-events:none;`; + el.textContent = `${pass ? "PASS" : "FAIL"}: ${details}`; + document.body.appendChild(el); + this._overlay = el; + setTimeout(() => { if (this._overlay === el) { el.remove(); this._overlay = null; } }, 5000); + } + + dispose(): void { + if (this._timeoutTimer) clearTimeout(this._timeoutTimer); + if (this._overlay) { this._overlay.remove(); this._overlay = null; } + } +} diff --git a/misc/DimSim/src/dimos/rubrics.ts b/misc/DimSim/src/dimos/rubrics.ts new file mode 100644 index 0000000000..fef455a42d --- /dev/null +++ b/misc/DimSim/src/dimos/rubrics.ts @@ -0,0 +1,156 @@ +/** + * Eval Rubrics — deterministic scoring for eval workflows. + * + * Rubrics: + * objectDistance — Euclidean distance from agent to target bbox surface + * radiusContains — agent is within radius of centroid computed from multiple targets + */ + +export interface Vec3 { x: number; y: number; z: number; } + +export interface AssetEntry { + title?: string; + id?: string; + transform?: { x?: number; y?: number; z?: number }; + _bbox?: { w: number; h: number; d: number }; +} + +export interface SceneState { + assets?: AssetEntry[]; + agentPos?: Vec3; +} + +export interface ObjectDistanceCriteria { + object: string; + target: string; + thresholdM?: number; +} + +export interface ObjectDistanceResult { + pass: boolean; + distanceM: number; + details: string; +} + +export function scoreObjectDistance(criteria: ObjectDistanceCriteria, sceneState: SceneState): ObjectDistanceResult { + const { target: targetName, thresholdM = 0.5 } = criteria; + + if (!sceneState.agentPos) { + return { pass: false, distanceM: Infinity, details: "Agent position not available" }; + } + + const targetHit = _findTarget(targetName, sceneState); + if (!targetHit) { + return { pass: false, distanceM: Infinity, details: `Target "${targetName}" not found in scene` }; + } + + const dist = _distToSurface(sceneState.agentPos, targetHit.pos, targetHit.bbox); + + return { + pass: dist <= thresholdM, + distanceM: Math.round(dist * 1000) / 1000, + details: `agent is ${dist.toFixed(3)}m from "${targetName}" surface (threshold: ${thresholdM}m)`, + }; +} + +// -- radiusContains ----------------------------------------------------------- + +export interface RadiusContainsCriteria { + object?: string; // defaults to "agent" + targets: string[]; // scene objects whose centroid defines the region + radiusM?: number; // max distance from centroid (default 3.0) +} + +export interface RadiusContainsResult { + pass: boolean; + distanceM: number; + centroid: Vec3; + foundTargets: string[]; + missingTargets: string[]; + details: string; +} + +export function scoreRadiusContains(criteria: RadiusContainsCriteria, sceneState: SceneState): RadiusContainsResult { + const { targets, radiusM = 3.0 } = criteria; + + const fail = (details: string): RadiusContainsResult => ({ + pass: false, distanceM: Infinity, centroid: { x: 0, y: 0, z: 0 }, + foundTargets: [], missingTargets: targets, details, + }); + + if (!sceneState.agentPos) return fail("Agent position not available"); + if (!targets || targets.length === 0) return fail("No targets specified"); + + const found: { name: string; pos: Vec3 }[] = []; + const missing: string[] = []; + for (const name of targets) { + const hit = _findTarget(name, sceneState); + if (hit) found.push({ name, pos: hit.pos }); + else missing.push(name); + } + + if (found.length < 2 && found.length < targets.length) { + // Need at least 2 targets found, or all if fewer than 2 specified + if (found.length === 0) return fail(`No targets found: ${missing.join(", ")}`); + } + + // Compute centroid of found targets + const centroid: Vec3 = { x: 0, y: 0, z: 0 }; + for (const f of found) { + centroid.x += f.pos.x; + centroid.y += f.pos.y; + centroid.z += f.pos.z; + } + centroid.x /= found.length; + centroid.y /= found.length; + centroid.z /= found.length; + + const dx = sceneState.agentPos.x - centroid.x; + const dy = sceneState.agentPos.y - centroid.y; + const dz = sceneState.agentPos.z - centroid.z; + const dist = Math.round(Math.sqrt(dx * dx + dy * dy + dz * dz) * 1000) / 1000; + + const foundNames = found.map((f) => f.name); + const pass = dist <= radiusM; + const missingNote = missing.length > 0 ? ` (missing: ${missing.join(", ")})` : ""; + + return { + pass, + distanceM: dist, + centroid, + foundTargets: foundNames, + missingTargets: missing, + details: `agent is ${dist.toFixed(3)}m from centroid of [${foundNames.join(", ")}]${missingNote} (radius: ${radiusM}m)`, + }; +} + +// -- helpers ------------------------------------------------------------------ + +function _distToSurface(from: Vec3, center: Vec3, bbox?: { w: number; h: number; d: number }): number { + if (!bbox) { + const dx = from.x - center.x, dy = from.y - center.y, dz = from.z - center.z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + const hw = bbox.w / 2, hh = bbox.h / 2, hd = bbox.d / 2; + const cx = Math.max(center.x - hw, Math.min(from.x, center.x + hw)); + const cy = Math.max(center.y - hh, Math.min(from.y, center.y + hh)); + const cz = Math.max(center.z - hd, Math.min(from.z, center.z + hd)); + const dx = from.x - cx, dy = from.y - cy, dz = from.z - cz; + return Math.sqrt(dx * dx + dy * dy + dz * dz); +} + +function _findTarget(name: string, sceneState: SceneState): { pos: Vec3; bbox?: { w: number; h: number; d: number } } | null { + const lower = name.toLowerCase(); + if (!sceneState.assets) return null; + for (const asset of sceneState.assets) { + if (asset.title?.toLowerCase().includes(lower) || asset.id?.toLowerCase().includes(lower)) { + if (asset.transform) { + return { + pos: { x: asset.transform.x || 0, y: asset.transform.y || 0, z: asset.transform.z || 0 }, + bbox: asset._bbox, + }; + } + } + } + return null; +} diff --git a/misc/DimSim/src/dimos/sceneEditor.ts b/misc/DimSim/src/dimos/sceneEditor.ts new file mode 100644 index 0000000000..e6d7cc1ca9 --- /dev/null +++ b/misc/DimSim/src/dimos/sceneEditor.ts @@ -0,0 +1,452 @@ +/** + * SceneEditor — Browser-side script execution engine. + * + * Receives {type: "exec", code, id?} commands via the DimosBridge control WS, + * evaluates user JS with full Three.js + Rapier globals exposed, and returns + * {type: "execResult", id, success, result?, error?}. + * + * Must NOT modify engine.js — hooks into DimosBridge WS the same way EvalHarness does. + */ + +import type { DimosBridge } from "./dimosBridge.ts"; + +export interface SceneEditorGlobals { + scene: any; // THREE.Scene + THREE: any; // Three.js namespace + RAPIER: any; // Rapier namespace (may be null until ensureRapierLoaded) + rapierWorld: any; // Rapier.World (may be null) + worldBody: any; // Fixed RigidBody for static colliders + renderer: any; // THREE.WebGLRenderer + camera: any; // THREE.PerspectiveCamera + agent: any; // Player agent (has getPosition, setPosition, group) + assets: any[]; // Scene assets array + assetsGroup: any; // THREE.Group containing loaded asset meshes + gltfLoader: any; // THREE GLTFLoader instance +} + +export interface SceneEditorOptions { + bridge: DimosBridge; + globals: SceneEditorGlobals; + channel?: string; +} + +// AsyncFunction constructor — allows top-level await in user scripts +const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + +export class SceneEditor { + bridge: DimosBridge; + globals: SceneEditorGlobals; + channel: string; + + constructor({ bridge, globals, channel }: SceneEditorOptions) { + this.bridge = bridge; + this.globals = globals; + this.channel = channel || ""; + this._hookBridgeMessages(); + } + + _hookBridgeMessages(): void { + const origConnect = this.bridge.connect.bind(this.bridge); + this.bridge.connect = () => { + origConnect(); + setTimeout(() => { + const ws = this.bridge.ws; + if (ws) this._patchWsOnMessage(ws); + }, 100); + }; + const ws = this.bridge.ws; + if (ws) this._patchWsOnMessage(ws); + } + + _patchWsOnMessage(ws: WebSocket): void { + const origOnMessage = ws.onmessage; + ws.onmessage = (event: MessageEvent) => { + if (typeof event.data === "string") { + try { + const cmd = JSON.parse(event.data); + if (cmd.type === "exec" || cmd.type === "loadScript") { + this._handleCommand(cmd); + return; + } + } catch { /* not JSON or not for us */ } + } + // Pass through to existing handlers (EvalHarness, DimosBridge) + if (origOnMessage) (origOnMessage as (e: MessageEvent) => void).call(ws, event); + }; + } + + _send(msg: Record): void { + if (this.channel) msg.channel = this.channel; + this.bridge.sendCommand(msg); + } + + async _handleCommand(cmd: { type: string; code?: string; url?: string; id?: string; channel?: string }): Promise { + if (this.channel && cmd.channel && cmd.channel !== this.channel) return; + + if (cmd.type === "exec" && cmd.code) { + await this._execCode(cmd.code, cmd.id); + } else if (cmd.type === "loadScript" && cmd.url) { + await this._loadScript(cmd.url, cmd.id); + } + } + + // Track colliders created by addCollider so removeCollider can clean up + _colliderMap: Map = new Map(); // mesh.uuid → Rapier collider + // Track dynamic rigid bodies (body + mesh ref for position sync) + _dynamicBodies: Map = new Map(); + // Track NPC mixers for animation updates + _npcMixers: Map = new Map(); // npc name → THREE.AnimationMixer + _npcAnimFrame: number | null = null; + _npcClock: any = null; // THREE.Clock + + async _execCode(code: string, id?: string): Promise { + console.log(`[sceneEditor] exec${id ? ` (${id})` : ""}:`, code.slice(0, 100)); + try { + const g = this.globals; + const colliderMap = this._colliderMap; + + // loadGLTF: convenience async helper for loading GLTF/GLB models + const loadGLTF = (url: string): Promise => + new Promise((resolve, reject) => + g.gltfLoader.load(url, resolve, undefined, reject), + ); + + // addCollider: create a physics collider for a mesh/group + // shape: "trimesh" (default) | "box" | "sphere" + // opts.dynamic: if true, creates a dynamic rigid body (responds to gravity/collisions) + // opts.mass: mass in kg (default 1.0, only for dynamic) + // opts.restitution: bounciness 0-1 (default 0.3, only for dynamic) + // Creates collider browser-side AND sends command to server (for lidar/physics) + const sendPhysics = this._send.bind(this); + const dynamicBodies = this._dynamicBodies; + const selfRef = this; + const addCollider = (obj: any, shapeOrOpts?: string | { shape?: string; dynamic?: boolean; mass?: number; restitution?: number }): any => { + let shape = "trimesh"; + let dynamic = false; + let mass = 1.0; + let restitution = 0.3; + if (typeof shapeOrOpts === "string") { + shape = shapeOrOpts; + } else if (shapeOrOpts) { + shape = shapeOrOpts.shape || "trimesh"; + dynamic = !!shapeOrOpts.dynamic; + mass = shapeOrOpts.mass ?? 1.0; + restitution = shapeOrOpts.restitution ?? 0.3; + } + + // Remove existing collider if any + removeCollider(obj); + + const bbox = new g.THREE.Box3().setFromObject(obj); + const size = new g.THREE.Vector3(); + const center = new g.THREE.Vector3(); + bbox.getSize(size); + bbox.getCenter(center); + + const clamp = (v: number) => Math.max(v, 0.001); + + // Build server-side descriptor (shape-agnostic) + const serverDesc: any = { + shape, + position: { x: center.x, y: center.y, z: center.z }, + }; + + if (shape === "sphere") { + const r = clamp(Math.max(size.x, size.y, size.z) / 2); + serverDesc.radius = r; + } else if (shape === "trimesh") { + const verts: number[] = []; + const indices: number[] = []; + let vertBase = 0; + obj.traverse((m: any) => { + if (!m.isMesh) return; + const geom = m.geometry; + const posAttr = geom?.attributes?.position; + if (!posAttr) return; + const tmpPos = new g.THREE.Vector3(); + for (let i = 0; i < posAttr.count; i++) { + tmpPos.fromBufferAttribute(posAttr, i).applyMatrix4(m.matrixWorld); + verts.push(tmpPos.x, tmpPos.y, tmpPos.z); + } + if (geom.index) { + for (let i = 0; i < geom.index.count; i++) indices.push(geom.index.getX(i) + vertBase); + } else { + for (let i = 0; i < posAttr.count; i++) indices.push(vertBase + i); + } + vertBase += posAttr.count; + }); + if (verts.length < 9 || indices.length < 3) throw new Error("Not enough geometry for trimesh"); + serverDesc.vertices = Array.from(verts); + serverDesc.indices = Array.from(indices); + } else { + // box (default) + serverDesc.halfExtents = { + x: clamp(size.x / 2), y: clamp(size.y / 2), z: clamp(size.z / 2), + }; + } + + // Browser-side collider (for standalone / non-dimos mode) + if (g.RAPIER && g.rapierWorld) { + let desc: any; + if (shape === "sphere") { + desc = g.RAPIER.ColliderDesc.ball(serverDesc.radius); + if (!dynamic) desc.setTranslation(center.x, center.y, center.z); + } else if (shape === "trimesh") { + desc = g.RAPIER.ColliderDesc.trimesh( + new Float32Array(serverDesc.vertices), new Uint32Array(serverDesc.indices) + ); + } else { + const h = serverDesc.halfExtents; + desc = g.RAPIER.ColliderDesc.cuboid(h.x, h.y, h.z); + if (!dynamic) desc.setTranslation(center.x, center.y, center.z); + } + desc.setFriction(0.9); + desc.setRestitution(restitution); + + if (dynamic && shape !== "trimesh") { + // Dynamic: create rigid body + attach collider + const bodyDesc = g.RAPIER.RigidBodyDesc.dynamic() + .setTranslation(center.x, center.y, center.z); + const body = g.rapierWorld.createRigidBody(bodyDesc); + body.setAdditionalMass(mass); + const collider = g.rapierWorld.createCollider(desc, body); + colliderMap.set(obj.uuid, collider); + dynamicBodies.set(obj.uuid, { body, mesh: obj }); + selfRef._ensureDynamicSyncLoop(); + } else { + // Static: collider with no parent body + const collider = g.rapierWorld.createCollider(desc); + colliderMap.set(obj.uuid, collider); + } + } + + // Server-side collider (for lidar + dimos physics) + serverDesc.dynamic = dynamic; + if (dynamic) { serverDesc.mass = mass; serverDesc.restitution = restitution; } + sendPhysics({ type: "physicsColliderAdd", uuid: obj.uuid, desc: serverDesc }); + + return { shape, dynamic, uuid: obj.uuid, size: { x: +size.x.toFixed(3), y: +size.y.toFixed(3), z: +size.z.toFixed(3) } }; + }; + + // removeCollider: remove collider browser-side + server-side + const removeCollider = (obj: any): boolean => { + const existing = colliderMap.get(obj.uuid); + if (existing) { + try { + g.rapierWorld?.removeCollider(existing, true); + } catch { /* already removed */ } + colliderMap.delete(obj.uuid); + } + // Clean up dynamic rigid body if any + const dynEntry = dynamicBodies.get(obj.uuid); + if (dynEntry) { + try { g.rapierWorld?.removeRigidBody(dynEntry.body); } catch { /* already removed */ } + dynamicBodies.delete(obj.uuid); + } + // Always tell server to remove (even if browser didn't have it) + sendPhysics({ type: "physicsColliderRemove", uuid: obj.uuid }); + return !!existing; + }; + + // addNPC: load an animated GLTF character, place it, and play an animation + const npcMixers = this._npcMixers; + const self = this; + const addNPC = async (opts: { + url: string; + name?: string; + position?: { x: number; y: number; z: number }; + rotation?: number; // yaw in radians + scale?: number; + animation?: string | number; // clip name or index (default: 0) + collider?: boolean; // add trimesh collider (default: true) + }): Promise => { + const gltf = await loadGLTF(opts.url); + const model = gltf.scene; + const npcName = opts.name || `npc-${Date.now().toString(36)}`; + model.name = npcName; + if (opts.position) model.position.set(opts.position.x, opts.position.y, opts.position.z); + if (opts.rotation != null) model.rotation.y = opts.rotation; + if (opts.scale != null) model.scale.setScalar(opts.scale); + model.traverse((child: any) => { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; } }); + g.scene.add(model); + + // Set up animation + let activeClipName = ""; + const clipNames: string[] = []; + if (gltf.animations && gltf.animations.length > 0) { + const mixer = new g.THREE.AnimationMixer(model); + npcMixers.set(npcName, mixer); + + for (const clip of gltf.animations) clipNames.push(clip.name); + // Store clips on model so they can be switched at runtime + model.animations = gltf.animations; + + // Select animation clip + let clipIndex = 0; + if (typeof opts.animation === "string") { + const idx = gltf.animations.findIndex((c: any) => c.name.toLowerCase().includes(opts.animation!.toString().toLowerCase())); + if (idx >= 0) clipIndex = idx; + } else if (typeof opts.animation === "number") { + clipIndex = Math.min(opts.animation, gltf.animations.length - 1); + } + + const clip = gltf.animations[clipIndex]; + activeClipName = clip.name; + const action = mixer.clipAction(clip); + action.play(); + + // Start the animation loop if not already running + self._ensureNpcAnimLoop(); + } + + // Add collider (default: trimesh) + let colliderInfo = null; + if (opts.collider !== false) { + colliderInfo = addCollider(model, "trimesh"); + } + + return { + name: npcName, + animations: clipNames, + activeAnimation: activeClipName, + collider: colliderInfo, + }; + }; + + // removeNPC: remove an NPC from scene and clean up its mixer + const removeNPC = (name: string): boolean => { + const obj = g.scene.getObjectByName(name); + if (!obj) return false; + // Stop animation mixer + const mixer = npcMixers.get(name); + if (mixer) { mixer.stopAllAction(); npcMixers.delete(name); } + // Remove collider + removeCollider(obj); + // Clear name before removal so getObjectByName won't find stale refs + obj.name = ""; + // Remove from scene + obj.traverse((child: any) => { + if (child.isMesh) { child.geometry?.dispose(); child.material?.dispose(); } + }); + g.scene.remove(obj); + return true; + }; + + // autoScale: detect cm/m mismatch and normalize model to scene scale. + // Heuristic: if bounding box exceeds targetMaxDim (default 50m) in any axis, + // assume the model is in centimeters and scale by 0.01. For intermediate cases + // (10-50m), scale proportionally so the largest dimension equals targetMaxDim. + // Returns the scale factor applied (1.0 if no change). + const autoScale = (obj: any, targetMaxDim = 50): number => { + const bbox = new g.THREE.Box3().setFromObject(obj); + const size = new g.THREE.Vector3(); + bbox.getSize(size); + const maxDim = Math.max(size.x, size.y, size.z); + if (maxDim <= 0.001) return 1.0; // degenerate + let scaleFactor = 1.0; + if (maxDim > targetMaxDim * 2) { + // Very large — likely centimeters (100x off) + scaleFactor = 0.01; + } else if (maxDim > targetMaxDim) { + // Moderately large — scale down proportionally + scaleFactor = targetMaxDim / maxDim; + } + if (scaleFactor !== 1.0) { + obj.scale.multiplyScalar(scaleFactor); + obj.updateMatrixWorld(true); + console.log(`[sceneEditor] autoScale: ${maxDim.toFixed(1)}m → ${(maxDim * scaleFactor).toFixed(1)}m (×${scaleFactor.toFixed(4)})`); + } + return scaleFactor; + }; + + const fn = new AsyncFunction( + "scene", "THREE", "RAPIER", "rapierWorld", "renderer", "camera", + "agent", "playerBody", "assets", "assetsGroup", + "loadGLTF", "addCollider", "removeCollider", "addNPC", "removeNPC", "autoScale", + code, + ); + const result = await fn( + g.scene, g.THREE, g.RAPIER, g.rapierWorld, g.renderer, g.camera, + g.agent, g.agent, g.assets, g.assetsGroup, + loadGLTF, addCollider, removeCollider, addNPC, removeNPC, autoScale, + ); + this._send({ type: "execResult", id, success: true, result: _serialize(result) }); + } catch (err: any) { + console.error("[sceneEditor] exec error:", err); + this._send({ type: "execResult", id, success: false, error: String(err) }); + } + } + + async _loadScript(url: string, id?: string): Promise { + console.log(`[sceneEditor] loadScript${id ? ` (${id})` : ""}:`, url); + try { + const resp = await fetch(url); + if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); + const code = await resp.text(); + await this._execCode(code, id); + } catch (err: any) { + console.error("[sceneEditor] loadScript error:", err); + this._send({ type: "execResult", id, success: false, error: String(err) }); + } + } + + _ensureNpcAnimLoop(): void { + if (this._npcAnimFrame != null) return; + this._startUpdateLoop(); + } + + _ensureDynamicSyncLoop(): void { + if (this._npcAnimFrame != null) return; + this._startUpdateLoop(); + } + + /** Shared rAF loop for NPC animations + dynamic body position sync. */ + _startUpdateLoop(): void { + if (this._npcAnimFrame != null) return; + if (!this._npcClock) { + this._npcClock = new this.globals.THREE.Clock(); + } + const tick = () => { + const hasWork = this._npcMixers.size > 0 || this._dynamicBodies.size > 0; + if (!hasWork) { + this._npcAnimFrame = null; + return; + } + const dt = this._npcClock.getDelta(); + + // Update NPC animations + for (const mixer of this._npcMixers.values()) { + mixer.update(dt); + } + + // Sync dynamic body positions → Three.js meshes + for (const { body, mesh } of this._dynamicBodies.values()) { + const t = body.translation(); + const r = body.rotation(); + mesh.position.set(t.x, t.y, t.z); + mesh.quaternion.set(r.x, r.y, r.z, r.w); + } + + this._npcAnimFrame = requestAnimationFrame(tick); + }; + this._npcAnimFrame = requestAnimationFrame(tick); + } + + dispose(): void { + if (this._npcAnimFrame != null) cancelAnimationFrame(this._npcAnimFrame); + for (const mixer of this._npcMixers.values()) mixer.stopAllAction(); + this._npcMixers.clear(); + this._dynamicBodies.clear(); + } +} + +/** Safely serialize a return value for JSON transport. */ +function _serialize(val: any): any { + if (val === undefined || val === null) return val; + if (typeof val === "number" || typeof val === "string" || typeof val === "boolean") return val; + if (Array.isArray(val)) return val.map(_serialize); + // Three.js objects have .toJSON() but it's huge — just return type + id + if (val.isObject3D) return { _type: "Object3D", type: val.type, name: val.name, uuid: val.uuid }; + if (val.isMesh) return { _type: "Mesh", name: val.name, uuid: val.uuid }; + try { return JSON.parse(JSON.stringify(val)); } catch { return String(val); } +} diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js new file mode 100644 index 0000000000..2f19688c03 --- /dev/null +++ b/misc/DimSim/src/engine.js @@ -0,0 +1,7763 @@ +import "./style.css"; + +import * as THREE from "three"; +import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls.js"; +import { AiAvatar } from "./AiAvatar.js"; +import { ACTIONS as SIM_VLM_ACTIONS, DEFAULTS as SIM_VLM_DEFAULTS } from "./ai/sim/vlmActions.js"; +import { buildPrompt as buildSimVlmPrompt } from "./ai/sim/vlmPrompt.js"; +import { MODEL_CONFIG } from "./ai/modelConfig.js"; +import { requestVlmDecision } from "./ai/vlmClient.js"; +import { captureAgentPovBase64, processPendingCaptures, hasPendingCapture } from "./ai/visionCapture.js"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js"; +import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js"; + +const ACTIVE_VLM_ACTIONS = SIM_VLM_ACTIONS; +const ACTIVE_VLM_DEFAULTS = SIM_VLM_DEFAULTS; +const buildActiveVlmPrompt = () => buildSimVlmPrompt({ actions: ACTIVE_VLM_ACTIONS }); +const resolveActiveVlmModel = () => MODEL_CONFIG.simMode; + +let threeRendererRef = null; +let threeSceneRef = null; +let RAPIER = null; +let _rapierInitPromise = null; +let rapierWorld = null; +let worldBody = null; +let playerBody = null; +let playerCollider = null; +let flyMode = true; +let ghostMode = true; +let characterController = null; +let _rapierStepFaultCount = 0; +let walkVerticalVel = 0; +let aiAgents = []; + +// Track asset collider handles for cleanup +const _assetColliderHandles = new Map(); + +// Player dimensions (tuned smaller so you can fit inside tighter splat/glb interiors). +const PLAYER_RADIUS = 0.12; +const PLAYER_HALF_HEIGHT = 0.25; +const PLAYER_EYE_HEIGHT = PLAYER_HALF_HEIGHT + PLAYER_RADIUS + 0.2; // camera above body origin +const LIDAR_MOUNT_HEIGHT = 0.35; // Go2 lidar mount height above ground +// Real Go2 front camera height above ground in its home/operating crouch pose. +// Used for agent-POV captures so the rendered view matches what the hardware +// camera would see (low, not eye-level). ~0.30 m matches the MuJoCo go2.xml +// home keyframe (thigh 0.9, calf -1.8) which the Go2 actually stands in. +const GO2_CAMERA_HEIGHT = 0.30; +// Go2 front RGB-D camera is mounted on the front of the head, forward of the +// body center. Offsetting places the camera origin outside the robot mesh so +// POV captures don't render the inside of the body. +const GO2_CAMERA_FORWARD = 0.18; + +const canvas = document.getElementById("c"); +const statusEl = document.getElementById("status"); +const resetBtn = document.getElementById("reset"); +const assetsListEl = document.getElementById("assets-list"); // null in sim-only +const overlayEl = document.getElementById("overlay"); +const simPanelCollapseBtn = document.getElementById("sim-panel-collapse"); +const simPanelOpenBtn = document.getElementById("sim-panel-open"); +const statusSimEl = document.getElementById("status-sim"); +const spawnAiBtn = document.getElementById("spawn-ai"); +const agentPanelEl = document.getElementById("agent-panel"); +const agentLastEl = document.getElementById("agent-last"); +const agentObservationEl = document.getElementById("agent-observation"); +const agentShotImgEl = document.getElementById("agent-shot-img"); +const agentReqMetaEl = document.getElementById("agent-req-meta"); +const agentReqPromptEl = document.getElementById("agent-req-prompt"); +const agentReqContextEl = document.getElementById("agent-req-context"); +const agentRespRawEl = document.getElementById("agent-resp-raw"); +const agentLogEl = document.getElementById("agent-log"); +const agentTaskStatusEl = document.getElementById("agent-task-status"); +const agentTaskInputEl = document.getElementById("agent-task-input"); +const agentTaskStartBtn = document.getElementById("agent-task-start"); +const agentTaskEndBtn = document.getElementById("agent-task-end"); +const simCameraModeToggleBtn = document.getElementById("sim-camera-toggle"); +const simViewRgbdBtn = document.getElementById("sim-view-rgbd"); +const simViewLidarBtn = document.getElementById("sim-view-lidar"); +const simViewCompareBtn = document.getElementById("sim-view-compare"); +const simRgbdGrayBtn = document.getElementById("sim-rgbd-gray"); +const simRgbdColormapBtn = document.getElementById("sim-rgbd-colormap"); +const simRgbdAutoRangeBtn = document.getElementById("sim-rgbd-auto-range"); +const simRgbdNoiseBtn = document.getElementById("sim-rgbd-noise"); +const simRgbdSpeckleBtn = document.getElementById("sim-rgbd-speckle"); +const simRgbdMinEl = document.getElementById("sim-rgbd-min"); +const simRgbdMaxEl = document.getElementById("sim-rgbd-max"); +const simRgbdMinValEl = document.getElementById("sim-rgbd-min-val"); +const simRgbdMaxValEl = document.getElementById("sim-rgbd-max-val"); +const simRgbdPcOverlayBtn = document.getElementById("sim-rgbd-pc-overlay"); +const simLidarColorRangeBtn = document.getElementById("sim-lidar-color-range"); +const simLidarOrderedDebugBtn = document.getElementById("sim-lidar-ordered-debug"); +const simLidarNoiseBtn = document.getElementById("sim-lidar-noise"); +const simLidarMultiReturnBtn = document.getElementById("sim-lidar-multireturn"); + +// ── dimos integration mode ────────────────────────────────────────────────── +// Activated via ?dimos=1 URL param or window.__dimosMode (injected by Deno bridge server). +// When active: internal VLM loop disabled, agent pose driven by external /odom, +// sensor data (RGB, depth, LiDAR) published as LCM packets via WebSocket bridge. +const _dimosParams = new URLSearchParams(window.location.search); +const dimosMode = _dimosParams.get("dimos") === "1" || window.__dimosMode === true; +if (dimosMode) document.body.classList.add("dimos-mode"); +const dimosScene = _dimosParams.get("scene") || window.__dimosScene || null; +let simSensorViewMode = "rgb"; // "rgb" | "rgbd" | "lidar" +let simCompareView = false; // show RGB + RGB-D + LiDAR side-by-side +let simPanelCollapsed = false; +let simUserCameraMode = localStorage.getItem("sparkWorldSimCameraMode") === "user" ? "user" : "agent"; +let rgbdVizMode = "colormap"; // "colormap" | "gray" +let rgbdAutoRange = true; +let rgbdRangeMinM = 0.2; +let rgbdRangeMaxM = 12.0; +let rgbdNoiseEnabled = false; +let rgbdSpeckleEnabled = false; +let rgbdPcOverlayOnLidar = false; +let lidarColorByRange = false; // false = intensity grayscale (realistic default) +let lidarOrderedDebugView = false; // false=unordered 3D cloud, true=ordered rings debug +let lidarNoiseEnabled = false; // deterministic range noise + dropouts +let lidarMultiReturnMode = "strongest"; // "strongest" | "last" +let worldKey = localStorage.getItem("sparkWorldLastWorldKey") ?? "default"; + +function clampNum(v, min, max) { + const n = Number(v); + if (!Number.isFinite(n)) return min; + return Math.min(max, Math.max(min, n)); +} + +function normalizeHexColor(value, fallback) { + try { + return `#${new THREE.Color(value).getHexString()}`; + } catch { + return fallback; + } +} + +function createDefaultSceneSettings() { + return { + sky: { + enabled: false, + topColor: "#7aa9ff", + horizonColor: "#cfe5ff", + bottomColor: "#f4f8ff", + brightness: 1.0, + softness: 1.35, + sunStrength: 0.18, + sunHeight: 0.45, + }, + }; +} + +function normalizeSceneSettings(raw) { + const defaults = createDefaultSceneSettings(); + const src = raw && typeof raw === "object" ? raw : {}; + const srcSky = src.sky && typeof src.sky === "object" ? src.sky : {}; + return { + sky: { + enabled: !!srcSky.enabled, + topColor: normalizeHexColor(srcSky.topColor, defaults.sky.topColor), + horizonColor: normalizeHexColor(srcSky.horizonColor, defaults.sky.horizonColor), + bottomColor: normalizeHexColor(srcSky.bottomColor, defaults.sky.bottomColor), + brightness: clampNum(srcSky.brightness, 0.2, 2.0), + softness: clampNum(srcSky.softness, 0.2, 3.0), + sunStrength: clampNum(srcSky.sunStrength, 0.0, 1.0), + sunHeight: clampNum(srcSky.sunHeight, -0.2, 1.0), + }, + }; +} + +function serializeSceneSettings() { + return normalizeSceneSettings(sceneSettings); +} + +let sceneSettings = createDefaultSceneSettings(); +let tags = []; +let selectedTagId = null; +let draftTag = null; // tag being edited/created +const tagsGroup = new THREE.Group(); +tagsGroup.name = "tagsGroup"; + +// Assets (Edit mode) +let assets = []; // [{id,title,notes,states:[{id,name,glbName,dataBase64,interactions:[{id,label,to}]}],currentStateId,actions:[{id,label,from,to}],transform:{...}, _colliderHandle?}] +let selectedAssetId = null; +const assetsGroup = new THREE.Group(); +assetsGroup.name = "assetsGroup"; +const gltfLoader = new GLTFLoader(); + +// ============================================================================= +// BLOB SHADOW – lightweight planar shadow for GLB assets (no shadow maps needed) +// ============================================================================= +// Procedural radial-gradient texture (created once, shared by all blob shadows) +let _blobShadowTexture = null; +let _blobShadowGeometry = null; + +function getBlobShadowTexture() { + if (_blobShadowTexture) return _blobShadowTexture; + const size = 128; + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + // Use a GRAYSCALE gradient: white = opaque shadow, black = transparent. + // This texture will be used as an alphaMap (only the luminance/R channel matters). + const ctx = canvas.getContext("2d"); + const gradient = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2); + gradient.addColorStop(0, "#ffffff"); // center: fully opaque + gradient.addColorStop(0.35, "#cccccc"); + gradient.addColorStop(0.65, "#444444"); + gradient.addColorStop(1, "#000000"); // edge: fully transparent + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, size, size); + _blobShadowTexture = new THREE.CanvasTexture(canvas); + _blobShadowTexture.needsUpdate = true; + return _blobShadowTexture; +} + +function getBlobShadowGeometry() { + if (_blobShadowGeometry) return _blobShadowGeometry; + _blobShadowGeometry = new THREE.PlaneGeometry(1, 1); + // Rotate so the plane lies flat on the XZ ground plane (face up) + _blobShadowGeometry.rotateX(-Math.PI / 2); + return _blobShadowGeometry; +} + +// Create a blob shadow mesh sized to an asset's footprint. +// Returns a Mesh that should be added as a child of the asset root. +// `opts` = { opacity, scale, stretch, rotationDeg, offsetX, offsetY, offsetZ } +function createBlobShadow(assetId, footprintX, footprintZ, localGroundY, opts) { + const o = opts || {}; + const userScale = o.scale ?? 1.0; + const userOpacity = o.opacity ?? 0.5; + const stretch = o.stretch ?? 1.0; // >1 elongates X, <1 elongates Z + const rotDeg = o.rotationDeg ?? 0; // rotation around Y in degrees + const offsetX = o.offsetX ?? 0; + const offsetY = o.offsetY ?? 0; + const offsetZ = o.offsetZ ?? 0; + + // Base diameter from asset footprint, then apply user scale + const baseDiameter = Math.max(footprintX, footprintZ) * 1.1; + const d = baseDiameter * userScale; + if (d < 0.04) return null; + + const mat = new THREE.MeshBasicMaterial({ + color: 0x000000, + alphaMap: getBlobShadowTexture(), + transparent: true, + depthWrite: false, + depthTest: true, + opacity: userOpacity, + side: THREE.DoubleSide, + // Use ONLY constant depth bias. Slope-based factor causes the blob to + // appear to slide as the camera angle changes while moving. + polygonOffset: true, + polygonOffsetFactor: 0, + polygonOffsetUnits: -300, + }); + const mesh = new THREE.Mesh(getBlobShadowGeometry(), mat); + // stretch > 1 makes the X axis wider; Z axis is inversely narrower to + // keep the overall area roughly constant. + const sx = d * stretch; + const sz = d / stretch; + mesh.scale.set(sx, 1, sz); + // Raise slightly so it stays on/just above floor. + mesh.position.set(offsetX, localGroundY + 0.08 + offsetY, offsetZ); + // The shared geometry is already rotated to lie on XZ. An additional Y + // rotation spins the ellipse around the vertical axis. + mesh.rotation.y = (rotDeg * Math.PI) / 180; + mesh.renderOrder = 1000; + mesh.castShadow = false; + mesh.receiveShadow = false; + mesh.name = `blobShadow:${assetId}`; + mesh.userData.isBlobShadow = true; + mesh.userData._baseDiameter = baseDiameter; + mesh.userData._baseLocalY = localGroundY + 0.08; + return mesh; +} + +// ============================================================================= +// PRIMITIVES (Level Editor) – lightweight parametric shapes +// ============================================================================= +let primitives = []; // [{id, type, name, dimensions:{...}, transform:{position,rotation,scale}, material:{color,roughness,metalness,textureDataUrl}, physics:bool, _colliderHandle?}] +let selectedPrimitiveId = null; +const _assetBumpVelocities = new Map(); // assetId -> THREE.Vector3 +const _playerPosPrevForBump = new THREE.Vector3(); +let _playerPosPrevForBumpValid = false; +const _agentPosPrevForBump = new Map(); // agentId -> THREE.Vector3 +let _lastBumpSaveAt = 0; +let _lastBumpColliderSyncAt = 0; +const primitivesGroup = new THREE.Group(); +primitivesGroup.name = "primitivesGroup"; + +const PRIMITIVE_DEFAULTS = { + box: { + width: 1, + height: 1, + depth: 1, + edgeRadius: 0, + edgeSegments: 4, + widthSegments: 1, + heightSegments: 1, + depthSegments: 1, + }, + sphere: { + radius: 0.5, + widthSegments: 32, + heightSegments: 16, + phiStartDeg: 0, + phiLengthDeg: 360, + thetaStartDeg: 0, + thetaLengthDeg: 180, + }, + cylinder: { radiusTop: 0.5, radiusBottom: 0.5, height: 1, radialSegments: 32, heightSegments: 1, openEnded: 0 }, + cone: { radius: 0.5, height: 1, radialSegments: 32, heightSegments: 1, openEnded: 0 }, + torus: { radius: 0.5, tube: 0.15, radialSegments: 16, tubularSegments: 48, arcDeg: 360 }, + plane: { width: 2, height: 2, widthSegments: 1, heightSegments: 1 }, +}; + +const PRIMITIVE_DIM_CONFIG = { + width: { min: 0.05, max: 50, step: 0.05 }, + height: { min: 0.05, max: 50, step: 0.05 }, + depth: { min: 0.05, max: 50, step: 0.05 }, + radius: { min: 0.01, max: 20, step: 0.01 }, + radiusTop: { min: 0.01, max: 20, step: 0.01 }, + radiusBottom: { min: 0.01, max: 20, step: 0.01 }, + tube: { min: 0.01, max: 10, step: 0.01 }, + edgeRadius: { min: 0, max: 2.5, step: 0.01 }, + edgeSegments: { min: 1, max: 12, step: 1, integer: true }, + widthSegments: { min: 1, max: 128, step: 1, integer: true }, + heightSegments: { min: 1, max: 128, step: 1, integer: true }, + depthSegments: { min: 1, max: 128, step: 1, integer: true }, + radialSegments: { min: 3, max: 128, step: 1, integer: true }, + tubularSegments: { min: 3, max: 256, step: 1, integer: true }, + phiStartDeg: { min: 0, max: 360, step: 1 }, + phiLengthDeg: { min: 1, max: 360, step: 1 }, + thetaStartDeg: { min: 0, max: 180, step: 1 }, + thetaLengthDeg: { min: 1, max: 180, step: 1 }, + arcDeg: { min: 1, max: 360, step: 1 }, + openEnded: { min: 0, max: 1, step: 1, integer: true }, +}; + +function formatPrimitiveDimValue(key, value) { + if (PRIMITIVE_DIM_CONFIG[key]?.integer) return String(Math.round(value)); + if (key.endsWith("Deg")) return `${Math.round(value)}°`; + if (key === "openEnded") return value >= 0.5 ? "Yes" : "No"; + return Number(value).toFixed(2); +} + +const PRIMITIVE_DIM_LABELS = { + edgeRadius: "Roundness", + edgeSegments: "Round Detail", + widthSegments: "Detail X", + heightSegments: "Detail Y", + depthSegments: "Detail Z", + radialSegments: "Circle Detail", + tubularSegments: "Ring Detail", + phiStartDeg: "Horizontal Cut Start", + phiLengthDeg: "Horizontal Fill", + thetaStartDeg: "Vertical Cut Start", + thetaLengthDeg: "Vertical Fill", + arcDeg: "Ring Opening", + openEnded: "Open Ends", + radiusTop: "Top Radius", + radiusBottom: "Bottom Radius", +}; + +function getPrimitiveDimLabel(key) { + return PRIMITIVE_DIM_LABELS[key] || key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()); +} + +// ============================================================================= +// EDITOR LIGHTS – user-placed lights with full control +// ============================================================================= +let editorLights = []; // [{id, type, name, color, intensity, position:{x,y,z}, target:{x,y,z}, distance, angle, penumbra, castShadow, _lightObj?, _helperObj?}] +let groups = []; // [{id, name, children:[primId,...], pickable?}] +const lightsGroup = new THREE.Group(); +lightsGroup.name = "lightsGroup"; +const _assetRaycaster = new THREE.Raycaster(); +const _agentAssetRaycaster = new THREE.Raycaster(); +const _tmpV1 = new THREE.Vector3(); +const _tmpV2 = new THREE.Vector3(); +const _tmpV3 = new THREE.Vector3(); + +// Agent camera follow mode (first-person POV) +let agentCameraFollow = false; +let _agentFollowInitialized = false; + +// Agent task state — per-agent tasks for parallel execution. +let agentTask = { + active: false, + instruction: "", + startedAt: 0, + finishedAt: 0, + finishedReason: "", + lastSummary: "", +}; +const _agentTasks = new Map(); // agentId -> { active, instruction, startedAt, finishedAt, finishedReason, lastSummary } + +function _getAgentTask(agentId) { + return _agentTasks.get(agentId) || agentTask; +} + +function _setAgentTask(agentId, task) { + _agentTasks.set(agentId, task); + // Keep global agentTask in sync with the most recent active task (for UI compat) + if (task.active) { + agentTask = { ...task }; + } +} +let selectedAgentInspectorId = null; +const agentInspectorStateById = new Map(); // id -> { shot, request, response } +let agentCameraFollowId = null; +let agentUiSelectedLabelEl = null; +let agentUiSpawnBtn = null; +let agentUiFollowBtn = null; +let agentUiStopBtn = null; +let agentUiRemoveBtn = null; +let agentUiTaskInputEl = null; +let agentUiTaskRunBtn = null; +let agentTaskTargetId = null; +let agentBadgeLayerEl = null; +const agentBadgeElsById = new Map(); +const MAX_AGENT_COUNT = 4; + +// ============================================================================= +// WORLD MANIFEST & LOADING +// ============================================================================= + +// Helper to normalize asset schema (backward compat) +// This function ensures all asset properties are properly loaded including states, interactions, and actions +function normalizeAssetSchema(raw) { + // Ensure states exist + if (!raw.states || raw.states.length === 0) { + raw.states = [{ + id: crypto.randomUUID(), + name: "default", + glbName: raw.glbName || "", + dataBase64: raw.dataBase64 || "", + interactions: [], + }]; + raw.currentStateId = raw.states[0].id; + } + + // Ensure each state has interactions array + for (const state of raw.states) { + if (!Array.isArray(state.interactions)) { + state.interactions = []; + } + } + + // Build the normalized asset object + const normalized = { + id: raw.id ?? crypto.randomUUID(), + title: raw.title ?? "", + notes: raw.notes ?? "", + states: raw.states, + currentStateId: raw.currentStateId ?? raw.states[0]?.id, + actions: [], // Will be populated below + transform: raw.transform ?? { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } }, + pickable: raw.pickable ?? false, // Can be picked up and moved + castShadow: raw.castShadow ?? false, + receiveShadow: raw.receiveShadow ?? false, + blobShadow: raw.blobShadow ?? null, // { opacity, scale, stretch, rotationDeg, offsetX, offsetY, offsetZ } + }; + + // Copy actions if they exist in raw data + if (Array.isArray(raw.actions) && raw.actions.length > 0) { + normalized.actions = raw.actions.map(act => ({ + id: act.id, + label: act.label || "toggle", + from: act.from, + to: act.to, + })); + } else { + // Backfill actions from state interactions if no actions array exists + for (const state of normalized.states) { + for (const interaction of state.interactions || []) { + if (interaction.to && interaction.to !== state.id) { + normalized.actions.push({ + id: interaction.id || `act_${state.id}_${interaction.to}`, + label: interaction.label || "toggle", + from: state.id, + to: interaction.to, + }); + } + } + } + } + + // Also backfill interactions from actions if any state is missing them + if (normalized.actions.length > 0) { + const actionsByFrom = new Map(); + for (const act of normalized.actions) { + if (!actionsByFrom.has(act.from)) actionsByFrom.set(act.from, []); + actionsByFrom.get(act.from).push({ id: act.id, label: act.label, to: act.to }); + } + for (const state of normalized.states) { + if (!state.interactions || state.interactions.length === 0) { + state.interactions = actionsByFrom.get(state.id) || []; + } + } + } + + return normalized; +} + +const renderer = new THREE.WebGLRenderer({ + canvas, + // SparkRenderer docs recommend antialias:false for splats (MSAA doesn't help splats and can hurt) + antialias: false, + powerPreference: "high-performance", + // Required for reading pixels from the canvas (agent POV capture) + preserveDrawingBuffer: true, +}); +renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); +renderer.setSize(window.innerWidth, window.innerHeight, false); +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.toneMappingExposure = 1.1; + +// Shadows: OFF by default. Enabled dynamically only when a light actually casts shadows. +// BasicShadowMap is fully deterministic (no PCF/stochastic filtering). +renderer.shadowMap.enabled = false; +renderer.shadowMap.type = THREE.BasicShadowMap; +renderer.shadowMap.autoUpdate = false; // we control when shadow maps update + +const clock = new THREE.Clock(); +const scene = new THREE.Scene(); +scene.background = new THREE.Color(0x06070a); + +// Image-based lighting for PBR GLBs. This dramatically improves "too dark" assets. +try { + const pmrem = new THREE.PMREMGenerator(renderer); + scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture; + pmrem.dispose(); +} catch { + // ignore +} + +// Make renderer/scene available to SparkRenderer initialization after dynamic import. +threeRendererRef = renderer; +threeSceneRef = scene; + +const camera = new THREE.PerspectiveCamera( + 65, + window.innerWidth / window.innerHeight, + 0.05, + 2000 +); +camera.position.set(0, 1.7, 4); + +// Lighting for non-splat geometry (assets/avatars). +// Splats are mostly self-lit visually; GLB assets need strong, stable fill to avoid looking black. +const ambientLight = new THREE.AmbientLight(0xffffff, 0.65); +scene.add(ambientLight); + +const hemi = new THREE.HemisphereLight(0xffffff, 0x223344, 0.85); +hemi.position.set(0, 10, 0); +scene.add(hemi); + +const dir = new THREE.DirectionalLight(0xffffff, 1.6); +dir.position.set(8, 14, 6); +dir.castShadow = false; // off by default; user enables via Scene Lighting panel +dir.shadow.mapSize.width = 512; +dir.shadow.mapSize.height = 512; +dir.shadow.camera.near = 0.5; +dir.shadow.camera.far = 40; +dir.shadow.camera.left = -15; +dir.shadow.camera.right = 15; +dir.shadow.camera.top = 15; +dir.shadow.camera.bottom = -15; +dir.shadow.bias = -0.003; +scene.add(dir); + +// Headlamp-style light attached to the camera so assets are visible wherever they are placed. +const headLamp = new THREE.PointLight(0xffffff, 1.4, 26, 1.5); +headLamp.position.set(0, 1.0, 0.6); +camera.add(headLamp); + +// Lightweight procedural sky dome (single draw call). This is intentionally +// simple so it remains cheap for scale/headless workloads. +const skyUniforms = { + uTop: { value: new THREE.Color("#7aa9ff") }, + uHorizon: { value: new THREE.Color("#cfe5ff") }, + uBottom: { value: new THREE.Color("#f4f8ff") }, + uBrightness: { value: 1.0 }, + uSoftness: { value: 1.35 }, + uSunStrength: { value: 0.18 }, + uSunDir: { value: new THREE.Vector3(0, 0.45, -1).normalize() }, +}; +const skyDome = new THREE.Mesh( + new THREE.SphereGeometry(220, 24, 16), + new THREE.ShaderMaterial({ + uniforms: skyUniforms, + side: THREE.BackSide, + depthWrite: false, + vertexShader: ` + varying vec3 vWorldDir; + void main() { + vec4 worldPos = modelMatrix * vec4(position, 1.0); + vWorldDir = normalize(worldPos.xyz - cameraPosition); + gl_Position = projectionMatrix * viewMatrix * worldPos; + } + `, + fragmentShader: ` + varying vec3 vWorldDir; + uniform vec3 uTop; + uniform vec3 uHorizon; + uniform vec3 uBottom; + uniform float uBrightness; + uniform float uSoftness; + uniform float uSunStrength; + uniform vec3 uSunDir; + void main() { + float h = clamp(vWorldDir.y * 0.5 + 0.5, 0.0, 1.0); + float shaped = pow(h, max(0.15, uSoftness)); + vec3 col = mix(uBottom, uHorizon, smoothstep(0.0, 0.55, shaped)); + col = mix(col, uTop, smoothstep(0.45, 1.0, shaped)); + float sun = pow(max(dot(normalize(vWorldDir), normalize(uSunDir)), 0.0), 220.0); + col += vec3(1.0, 0.92, 0.78) * sun * uSunStrength; + gl_FragColor = vec4(col * uBrightness, 1.0); + } + `, + }) +); +skyDome.frustumCulled = false; +skyDome.renderOrder = -1000; +skyDome.visible = false; +scene.add(skyDome); + + +// Registry of built-in scene lights so the editor can expose them +const sceneLights = [ + { id: "_ambient", label: "Ambient", obj: ambientLight, type: "ambient" }, + { id: "_hemi", label: "Hemisphere", obj: hemi, type: "hemisphere" }, + { id: "_dir", label: "Directional", obj: dir, type: "directional" }, + { id: "_headlamp", label: "Head Lamp", obj: headLamp, type: "point" }, + { id: "_sky", label: "Sky", obj: skyDome, type: "sky" }, +]; +scene.add(camera); + +// Avatar: simple capsule that follows the first-person camera. +const avatar = new THREE.Mesh( + new THREE.CapsuleGeometry(PLAYER_RADIUS * 0.8, PLAYER_HALF_HEIGHT * 2.0, 6, 12), + new THREE.MeshStandardMaterial({ color: 0x7cc4ff, roughness: 0.5 }) +); +avatar.castShadow = false; +avatar.receiveShadow = false; +avatar.visible = false; // always hidden; physics capsule handles collision +scene.add(avatar); +scene.add(tagsGroup); +scene.add(assetsGroup); +scene.add(primitivesGroup); +scene.add(lightsGroup); + + +// ----------------------------------------------------------------------------- +// Sim sensor view modes (deterministic + lightweight) +// ----------------------------------------------------------------------------- +const DEFAULT_SCENE_BG = new THREE.Color(0x06070a); +const RGBD_BG = new THREE.Color(0x000000); +function applySceneSkySettings() { + const s = normalizeSceneSettings(sceneSettings).sky; + sceneSettings.sky = s; + skyUniforms.uTop.value.set(s.topColor); + skyUniforms.uHorizon.value.set(s.horizonColor); + skyUniforms.uBottom.value.set(s.bottomColor); + skyUniforms.uBrightness.value = s.brightness; + skyUniforms.uSoftness.value = s.softness; + skyUniforms.uSunStrength.value = s.sunStrength; + skyUniforms.uSunDir.value.set(0, s.sunHeight, -1).normalize(); +} +function applySceneRgbBackground() { + if (sceneSettings.sky.enabled) { + skyDome.visible = true; + scene.background = null; + } else { + skyDome.visible = false; + scene.background = DEFAULT_SCENE_BG; + } +} +applySceneSkySettings(); +// RGB-D visualization range tuned for indoor robotics scenes (meters). +const RGBD_MIN_DEPTH_M = 0.2; +const RGBD_MAX_DEPTH_M = 12.0; +const RGBD_AUTO_PERCENTILE_LOW = 0.05; +const RGBD_AUTO_PERCENTILE_HIGH = 0.95; +const RGBD_AUTO_RANGE_UPDATE_MS = 250; +const RGBD_AUTO_RANGE_SMOOTH = 0.2; +const RGBD_CLEAR_ALPHA = 1.0; +rgbdRangeMinM = RGBD_MIN_DEPTH_M; +rgbdRangeMaxM = RGBD_MAX_DEPTH_M; +const _rgbdSize = new THREE.Vector2( + Math.max(1, Math.floor(window.innerWidth * renderer.getPixelRatio())), + Math.max(1, Math.floor(window.innerHeight * renderer.getPixelRatio())) +); +const rgbdDepthTarget = new THREE.WebGLRenderTarget(_rgbdSize.x, _rgbdSize.y, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + type: THREE.UnsignedByteType, + depthBuffer: true, + stencilBuffer: false, +}); +rgbdDepthTarget.texture.generateMipmaps = false; +rgbdDepthTarget.depthTexture = new THREE.DepthTexture(_rgbdSize.x, _rgbdSize.y, THREE.UnsignedIntType); +rgbdDepthTarget.depthTexture.minFilter = THREE.NearestFilter; +rgbdDepthTarget.depthTexture.magFilter = THREE.NearestFilter; +rgbdDepthTarget.depthTexture.generateMipmaps = false; +const RGBD_PC_OVERLAY_RT_W = 192; +const RGBD_PC_OVERLAY_RT_H = 108; +const rgbdOverlayDepthTarget = new THREE.WebGLRenderTarget(RGBD_PC_OVERLAY_RT_W, RGBD_PC_OVERLAY_RT_H, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + type: THREE.UnsignedByteType, + depthBuffer: true, + stencilBuffer: false, +}); +rgbdOverlayDepthTarget.texture.generateMipmaps = false; +rgbdOverlayDepthTarget.depthTexture = new THREE.DepthTexture(RGBD_PC_OVERLAY_RT_W, RGBD_PC_OVERLAY_RT_H, THREE.UnsignedIntType); +rgbdOverlayDepthTarget.depthTexture.minFilter = THREE.NearestFilter; +rgbdOverlayDepthTarget.depthTexture.magFilter = THREE.NearestFilter; +rgbdOverlayDepthTarget.depthTexture.generateMipmaps = false; + +// RGB-D debug material (planar forward-axis depth from view-space z). +// Kept only for debugging and no longer used as default RGB-D output. +const rgbdPlanarDepthDebugMaterial = new THREE.ShaderMaterial({ + uniforms: { + uMinDepth: { value: RGBD_MIN_DEPTH_M }, + uMaxDepth: { value: RGBD_MAX_DEPTH_M }, + }, + vertexShader: ` + varying float vLinearDepth; + void main() { + vec4 mv = modelViewMatrix * vec4(position, 1.0); + vLinearDepth = -mv.z; + gl_Position = projectionMatrix * mv; + } + `, + fragmentShader: ` + varying float vLinearDepth; + uniform float uMinDepth; + uniform float uMaxDepth; + void main() { + // Blend linear + inverse depth for strong near-range sensitivity while + // preserving metric ordering (deterministic, no auto-exposure). + float d = clamp(vLinearDepth, uMinDepth, uMaxDepth); + float lin = (d - uMinDepth) / max(0.0001, (uMaxDepth - uMinDepth)); // 0 near, 1 far + float inv = (1.0 / d - 1.0 / uMaxDepth) / max(0.0001, (1.0 / uMinDepth - 1.0 / uMaxDepth)); // 1 near, 0 far + float t = clamp(0.35 * (1.0 - lin) + 0.65 * inv, 0.0, 1.0); // near -> 1, far -> 0 + + // High-contrast pseudo-color ramp (near cyan/green, far orange/red) + vec3 nearC = vec3(0.05, 0.98, 0.98); + vec3 midC = vec3(0.40, 0.95, 0.10); + vec3 farC = vec3(0.98, 0.15, 0.05); + vec3 c = (t > 0.5) ? mix(midC, nearC, (t - 0.5) * 2.0) : mix(farC, midC, t * 2.0); + gl_FragColor = vec4(c, 1.0); + } + `, +}); +rgbdPlanarDepthDebugMaterial.toneMapped = false; + +// Fullscreen passes: +// 1) reconstruct metric camera-space Z into a float render target +// 2) visualize that metric depth for display +const rgbdPostCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); +const rgbdMetricUsesR32F = renderer.capabilities.isWebGL2 && !!renderer.extensions.get("EXT_color_buffer_float"); +const rgbdMetricTargetType = rgbdMetricUsesR32F ? THREE.FloatType : THREE.HalfFloatType; +const rgbdMetricTarget = new THREE.WebGLRenderTarget(_rgbdSize.x, _rgbdSize.y, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: rgbdMetricUsesR32F ? THREE.RedFormat : THREE.RGBAFormat, + type: rgbdMetricTargetType, + depthBuffer: false, + stencilBuffer: false, +}); +if (rgbdMetricUsesR32F) rgbdMetricTarget.texture.internalFormat = "R32F"; +rgbdMetricTarget.texture.generateMipmaps = false; +const rgbdOverlayMetricTarget = new THREE.WebGLRenderTarget(RGBD_PC_OVERLAY_RT_W, RGBD_PC_OVERLAY_RT_H, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: rgbdMetricUsesR32F ? THREE.RedFormat : THREE.RGBAFormat, + type: rgbdMetricTargetType, + depthBuffer: false, + stencilBuffer: false, +}); +if (rgbdMetricUsesR32F) rgbdOverlayMetricTarget.texture.internalFormat = "R32F"; +rgbdOverlayMetricTarget.texture.generateMipmaps = false; + +const rgbdMetricScene = new THREE.Scene(); +const rgbdMetricMaterial = new THREE.ShaderMaterial({ + uniforms: { + uDepthTex: { value: rgbdDepthTarget.depthTexture }, + uNear: { value: camera.near }, + uFar: { value: camera.far }, + uMinDepth: { value: rgbdRangeMinM }, + uMaxDepth: { value: rgbdRangeMaxM }, + uNoiseEnabled: { value: 0.0 }, + uSpeckleEnabled: { value: 0.0 }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position.xy, 0.0, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + uniform sampler2D uDepthTex; + uniform float uNear; + uniform float uFar; + uniform float uMinDepth; + uniform float uMaxDepth; + uniform float uNoiseEnabled; + uniform float uSpeckleEnabled; + + // Perspective depth [0,1] -> view-space z (negative in front of camera). + float perspectiveDepthToViewZ(const in float depth, const in float near, const in float far) { + return (near * far) / ((far - near) * depth - far); + } + + float hash12(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 0.1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); + } + + void main() { + float depth01 = texture2D(uDepthTex, vUv).x; + // No geometry hit: treat as max range. + if (depth01 >= 0.999999) { + gl_FragColor = vec4(uMaxDepth, uMaxDepth, uMaxDepth, 1.0); + return; + } + + float viewZ = perspectiveDepthToViewZ(depth01, uNear, uFar); + float zMetric = -viewZ; // camera-space Z in meters (robotics back-projection convention) + float d = clamp(zMetric, uMinDepth, uMaxDepth); + + if (uNoiseEnabled > 0.5) { + float span = max(0.0001, uMaxDepth - uMinDepth); + float t = clamp((d - uMinDepth) / span, 0.0, 1.0); + // Quantization: ~1mm near, up to ~8mm far (indoors). + float q = mix(0.001, 0.008, t * t); + d = floor(d / q + 0.5) * q; + + // Dropouts: more likely on edges and farther range. + float edge = clamp(length(vec2(dFdx(depth01), dFdy(depth01))) * 250.0, 0.0, 1.0); + float pDrop = 0.01 + 0.08 * t * t + 0.18 * edge; + float u = hash12(vUv * vec2(4096.0, 4096.0)); + if (u < pDrop) { + gl_FragColor = vec4(uMaxDepth, uMaxDepth, uMaxDepth, 1.0); + return; + } + + // Optional speckle noise (small multiplicative perturbation). + if (uSpeckleEnabled > 0.5) { + float n = hash12(vUv * vec2(8192.0, 8192.0) + vec2(17.3, 9.1)) - 0.5; + float amp = 0.002 + 0.01 * t; // 2mm near -> 12mm far + d = clamp(d + n * amp, uMinDepth, uMaxDepth); + } + } + + gl_FragColor = vec4(d, d, d, 1.0); + } + `, + depthTest: false, + depthWrite: false, +}); +rgbdMetricMaterial.toneMapped = false; +const rgbdMetricQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), rgbdMetricMaterial); +rgbdMetricScene.add(rgbdMetricQuad); + +const rgbdVizScene = new THREE.Scene(); +const rgbdVizMaterial = new THREE.ShaderMaterial({ + uniforms: { + uMetricDepthTex: { value: rgbdMetricTarget.texture }, + uMinDepth: { value: rgbdRangeMinM }, + uMaxDepth: { value: rgbdRangeMaxM }, + uGrayMode: { value: 0.0 }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position.xy, 0.0, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + uniform sampler2D uMetricDepthTex; + uniform float uMinDepth; + uniform float uMaxDepth; + uniform float uGrayMode; + void main() { + float d = texture2D(uMetricDepthTex, vUv).r; + d = clamp(d, uMinDepth, uMaxDepth); + float lin = (d - uMinDepth) / max(0.0001, (uMaxDepth - uMinDepth)); // 0 near, 1 far + if (uGrayMode > 0.5) { + float g = 1.0 - lin; + gl_FragColor = vec4(g, g, g, 1.0); + return; + } + float inv = (1.0 / d - 1.0 / uMaxDepth) / max(0.0001, (1.0 / uMinDepth - 1.0 / uMaxDepth)); // 1 near, 0 far + float t = clamp(0.35 * (1.0 - lin) + 0.65 * inv, 0.0, 1.0); // near -> 1, far -> 0 + vec3 nearC = vec3(0.05, 0.98, 0.98); + vec3 midC = vec3(0.40, 0.95, 0.10); + vec3 farC = vec3(0.98, 0.15, 0.05); + vec3 c = (t > 0.5) ? mix(midC, nearC, (t - 0.5) * 2.0) : mix(farC, midC, t * 2.0); + gl_FragColor = vec4(c, 1.0); + } + `, + depthTest: false, + depthWrite: false, +}); +rgbdVizMaterial.toneMapped = false; +const rgbdVizQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), rgbdVizMaterial); +rgbdVizScene.add(rgbdVizQuad); +let _savedOverrideMaterial = null; + +function resizeRgbdTargets() { + const w = Math.max(1, Math.floor(window.innerWidth * renderer.getPixelRatio())); + const h = Math.max(1, Math.floor(window.innerHeight * renderer.getPixelRatio())); + rgbdDepthTarget.setSize(w, h); + rgbdMetricTarget.setSize(w, h); + if (rgbdDepthTarget.depthTexture) { + rgbdDepthTarget.depthTexture.image.width = w; + rgbdDepthTarget.depthTexture.image.height = h; + rgbdDepthTarget.depthTexture.needsUpdate = true; + } +} + +let _rgbdNearFarAsserted = false; +let _rgbdLastAutoRangeMs = 0; + +function updateRgbdRangeLabels() { + if (simRgbdMinValEl) simRgbdMinValEl.textContent = `${rgbdRangeMinM.toFixed(1)}m`; + if (simRgbdMaxValEl) simRgbdMaxValEl.textContent = `${rgbdRangeMaxM.toFixed(1)}m`; +} + +function setRgbdRange(minD, maxD) { + const lo = Math.max(0.05, Math.min(minD, maxD - 0.05)); + const hi = Math.max(lo + 0.05, maxD); + rgbdRangeMinM = lo; + rgbdRangeMaxM = hi; + rgbdMetricMaterial.uniforms.uMinDepth.value = lo; + rgbdMetricMaterial.uniforms.uMaxDepth.value = hi; + rgbdVizMaterial.uniforms.uMinDepth.value = lo; + rgbdVizMaterial.uniforms.uMaxDepth.value = hi; + if (simRgbdMinEl) simRgbdMinEl.value = lo.toFixed(1); + if (simRgbdMaxEl) simRgbdMaxEl.value = hi.toFixed(1); + updateRgbdRangeLabels(); +} + +setRgbdRange(RGBD_MIN_DEPTH_M, RGBD_MAX_DEPTH_M); + +function percentileFromSorted(sorted, p) { + if (!sorted.length) return 0; + const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(p * (sorted.length - 1)))); + return sorted[idx]; +} + +function updateRgbdAutoRangeFromMetricTarget() { + const now = performance.now(); + if (now - _rgbdLastAutoRangeMs < RGBD_AUTO_RANGE_UPDATE_MS) return; + _rgbdLastAutoRangeMs = now; + const depth = readRgbdMetricDepthFrameMeters(); + if (!depth || depth.length === 0) return; + const samples = []; + const stride = Math.max(1, Math.floor(depth.length / 5000)); + for (let i = 0; i < depth.length; i += stride) { + const d = depth[i]; + if (!Number.isFinite(d)) continue; + if (d <= RGBD_MIN_DEPTH_M || d >= RGBD_MAX_DEPTH_M) continue; + samples.push(d); + } + if (samples.length < 32) return; + samples.sort((a, b) => a - b); + const p05 = percentileFromSorted(samples, RGBD_AUTO_PERCENTILE_LOW); + const p95 = percentileFromSorted(samples, RGBD_AUTO_PERCENTILE_HIGH); + const targetMin = Math.max(RGBD_MIN_DEPTH_M, Math.min(p05, p95 - 0.1)); + const targetMax = Math.min(RGBD_MAX_DEPTH_M, Math.max(p95, targetMin + 0.1)); + const smoothMin = rgbdRangeMinM + (targetMin - rgbdRangeMinM) * RGBD_AUTO_RANGE_SMOOTH; + const smoothMax = rgbdRangeMaxM + (targetMax - rgbdRangeMaxM) * RGBD_AUTO_RANGE_SMOOTH; + setRgbdRange(smoothMin, smoothMax); +} + +function renderRgbdView(enableAutoRange = true) { + renderRgbdMetricPassOffscreen(); + + if (enableAutoRange && rgbdAutoRange) updateRgbdAutoRangeFromMetricTarget(); + rgbdVizMaterial.uniforms.uGrayMode.value = rgbdVizMode === "gray" ? 1.0 : 0.0; + + // Pass 3: visualize metric depth target. + renderer.setRenderTarget(null); + renderer.setClearColor(RGBD_BG, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(rgbdVizScene, rgbdPostCamera); +} + +function renderRgbdMetricPassOffscreen(overrideCamera) { + const cam = overrideCamera || camera; + rgbdMetricMaterial.uniforms.uNear.value = cam.near; + rgbdMetricMaterial.uniforms.uFar.value = cam.far; + rgbdMetricMaterial.uniforms.uNoiseEnabled.value = rgbdNoiseEnabled ? 1.0 : 0.0; + rgbdMetricMaterial.uniforms.uSpeckleEnabled.value = rgbdSpeckleEnabled ? 1.0 : 0.0; + if (!_rgbdNearFarAsserted && !overrideCamera) { + console.assert( + Math.abs(rgbdMetricMaterial.uniforms.uNear.value - camera.near) < 1e-9 && + Math.abs(rgbdMetricMaterial.uniforms.uFar.value - camera.far) < 1e-9, + "[RGB-D] Reconstruction near/far must match active camera near/far." + ); + _rgbdNearFarAsserted = true; + } + + // Ensure depth pass sees scene geometry, not lidar/overlay debug points. + const savedOverride = scene.overrideMaterial; + const savedAssets = assetsGroup.visible; + const savedPrims = primitivesGroup.visible; + const savedLights = lightsGroup.visible; + const savedTags = tagsGroup.visible; + const savedLidarViz = lidarVizGroup.visible; + const savedRgbdPc = rgbdPcOverlayGroup.visible; + + scene.overrideMaterial = null; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + + renderer.setRenderTarget(rgbdDepthTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(scene, cam); + + renderer.setRenderTarget(rgbdMetricTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(rgbdMetricScene, rgbdPostCamera); + + scene.overrideMaterial = savedOverride; + assetsGroup.visible = savedAssets; + primitivesGroup.visible = savedPrims; + lightsGroup.visible = savedLights; + tagsGroup.visible = savedTags; + lidarVizGroup.visible = savedLidarViz; + rgbdPcOverlayGroup.visible = savedRgbdPc; +} + +function halfToFloat(h) { + const s = (h & 0x8000) >> 15; + const e = (h & 0x7c00) >> 10; + const f = h & 0x03ff; + if (e === 0) return (s ? -1 : 1) * Math.pow(2, -14) * (f / 1024); + if (e === 31) return f ? NaN : ((s ? -1 : 1) * Infinity); + return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / 1024); +} + +function readRgbdMetricDepthFrameMeters() { + const w = rgbdMetricTarget.width; + const h = rgbdMetricTarget.height; + if (!w || !h) return null; + + if (rgbdMetricUsesR32F) { + const depth = new Float32Array(w * h); + renderer.readRenderTargetPixels(rgbdMetricTarget, 0, 0, w, h, depth); + return depth; + } + + if (rgbdMetricTarget.texture.type === THREE.FloatType) { + const raw = new Float32Array(w * h * 4); + renderer.readRenderTargetPixels(rgbdMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = raw[i * 4 + 0]; + return depth; + } + + // Half-float fallback (WebGL1 / constrained platforms) + const raw = new Uint16Array(w * h * 4); + renderer.readRenderTargetPixels(rgbdMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = halfToFloat(raw[i * 4 + 0]); + return depth; +} + +function readRgbdOverlayMetricDepthFrameMeters() { + const w = rgbdOverlayMetricTarget.width; + const h = rgbdOverlayMetricTarget.height; + if (!w || !h) return null; + if (rgbdMetricUsesR32F) { + const depth = new Float32Array(w * h); + renderer.readRenderTargetPixels(rgbdOverlayMetricTarget, 0, 0, w, h, depth); + return depth; + } + if (rgbdOverlayMetricTarget.texture.type === THREE.FloatType) { + const raw = new Float32Array(w * h * 4); + renderer.readRenderTargetPixels(rgbdOverlayMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = raw[i * 4 + 0]; + return depth; + } + const raw = new Uint16Array(w * h * 4); + renderer.readRenderTargetPixels(rgbdOverlayMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = halfToFloat(raw[i * 4 + 0]); + return depth; +} + +function updateRgbdPcOverlayCloud(force = false) { + if (!rgbdPcOverlayOnLidar || simSensorViewMode !== "lidar" || lidarOrderedDebugView) { + _rgbdPcGeom.setDrawRange(0, 0); + _rgbdPcGeom.attributes.position.needsUpdate = true; + _rgbdPcGeom.attributes.color.needsUpdate = true; + _rgbdPcOverlayLastCount = 0; + _rgbdPcOverlayLastPose = null; + _rgbdPcOverlayDirty = false; + rgbdPcOverlayGroup.visible = false; + return; + } + if (!force && !_rgbdPcOverlayDirty) return; + const now = performance.now(); + const curPos = camera.getWorldPosition(new THREE.Vector3()); + const curQuat = camera.getWorldQuaternion(new THREE.Quaternion()); + if (_rgbdPcOverlayLastPose) { + const dp = curPos.distanceTo(_rgbdPcOverlayLastPose.pos); + const da = THREE.MathUtils.radToDeg(curQuat.angleTo(_rgbdPcOverlayLastPose.quat)); + if (!force && dp < RGBD_PC_OVERLAY_MIN_TRANSLATION_M && da < RGBD_PC_OVERLAY_MIN_ROT_DEG) return; + } + _rgbdPcOverlayLastUpdateMs = now; + _rgbdPcOverlayLastPose = { pos: curPos.clone(), quat: curQuat.clone() }; + + const savedDepthTex = rgbdMetricMaterial.uniforms.uDepthTex.value; + + // Low-res depth+metric pass for overlay to avoid expensive full-res readback stalls. + const savedOverride = scene.overrideMaterial; + const savedAssets = assetsGroup.visible; + const savedPrims = primitivesGroup.visible; + const savedLights = lightsGroup.visible; + const savedTags = tagsGroup.visible; + const savedLidarViz = lidarVizGroup.visible; + const savedRgbdPc = rgbdPcOverlayGroup.visible; + scene.overrideMaterial = null; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + + renderer.setRenderTarget(rgbdOverlayDepthTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(scene, camera); + rgbdMetricMaterial.uniforms.uDepthTex.value = rgbdOverlayDepthTarget.depthTexture; + renderer.setRenderTarget(rgbdOverlayMetricTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(rgbdMetricScene, rgbdPostCamera); + rgbdMetricMaterial.uniforms.uDepthTex.value = savedDepthTex; + + scene.overrideMaterial = savedOverride; + assetsGroup.visible = savedAssets; + primitivesGroup.visible = savedPrims; + lightsGroup.visible = savedLights; + tagsGroup.visible = savedTags; + lidarVizGroup.visible = savedLidarViz; + rgbdPcOverlayGroup.visible = savedRgbdPc; + + const depth = readRgbdOverlayMetricDepthFrameMeters(); + if (!depth) { + rgbdPcOverlayGroup.visible = false; + return; + } + const w = rgbdOverlayMetricTarget.width; + const h = rgbdOverlayMetricTarget.height; + if (!w || !h) return; + + const tanHalfY = Math.tan(THREE.MathUtils.degToRad(camera.fov * 0.5)); + const fy = 0.5 * h / Math.max(1e-6, tanHalfY); + const fx = fy * camera.aspect; + const cx = (w - 1) * 0.5; + const cy = (h - 1) * 0.5; + const targetCount = Math.min(RGBD_PC_OVERLAY_MAX_POINTS, Math.floor(w * h)); + const stride = Math.max(1, Math.floor(Math.sqrt((w * h) / Math.max(1, targetCount)))); + const pCam = new THREE.Vector3(); + const pWorld = new THREE.Vector3(); + + let n = 0; + for (let py = 0; py < h; py += stride) { + const v = h - 1 - py; // flip Y because render-target readback is bottom-up + for (let px = 0; px < w; px += stride) { + if (n >= RGBD_PC_OVERLAY_MAX_POINTS) break; + const d = depth[py * w + px]; + if (!Number.isFinite(d) || d <= RGBD_MIN_DEPTH_M || d >= RGBD_MAX_DEPTH_M) continue; + const x = ((px - cx) / fx) * d; + const y = -((v - cy) / fy) * d; + const z = -d; // camera forward is -Z in three.js camera coordinates + pCam.set(x, y, z); + pWorld.copy(pCam).applyMatrix4(camera.matrixWorld); + + _rgbdPcPosArray[n * 3 + 0] = pWorld.x; + _rgbdPcPosArray[n * 3 + 1] = pWorld.y; + _rgbdPcPosArray[n * 3 + 2] = pWorld.z; + + _rgbdPcColArray[n * 3 + 0] = 0.10; + _rgbdPcColArray[n * 3 + 1] = 1.00; + _rgbdPcColArray[n * 3 + 2] = 0.25; + n++; + } + if (n >= RGBD_PC_OVERLAY_MAX_POINTS) break; + } + + _rgbdPcGeom.setDrawRange(0, n); + _rgbdPcGeom.attributes.position.needsUpdate = true; + _rgbdPcGeom.attributes.color.needsUpdate = true; + _rgbdPcOverlayLastCount = n; + _rgbdPcOverlayDirty = false; + rgbdPcOverlayGroup.visible = rgbdPcOverlayOnLidar && simSensorViewMode === "lidar" && !lidarOrderedDebugView && n > 0; +} + +// ----------------------------------------------------------------------------- +// RoboVal standardized LiDAR schema + sensor model +// ----------------------------------------------------------------------------- +// We use lidar->world pose convention for pose_T_world_lidar (T_w_l). +// i.e. p_world = T_w_l * p_lidar +// Livox Mid-360 sensor model (non-repetitive Fibonacci scan pattern) +const LIDAR_SCAN_DURATION_S = 0.1; // 10 Hz scan rate +const LIDAR_NUM_POINTS = 10000; // points per scan +const LIDAR_MAX_POINTS = LIDAR_NUM_POINTS; +const LIDAR_MIN_RANGE_M = 0.1; // Mid-360: 0.1m min +const LIDAR_MAX_RANGE_M = 5; +const LIDAR_V_MIN_RAD = THREE.MathUtils.degToRad(-30); // sees ground ~0.6m from robot +const LIDAR_V_MAX_RAD = THREE.MathUtils.degToRad(15); // 15° up avoids ceiling, focuses rays on walls/ground +// Legacy constants kept for browser UI range image (not used by dimos path) +const LIDAR_NUM_RINGS = 1; +const LIDAR_RANGE_IMAGE_W = 1; +let _lidarScanCount = 0; + +// Pre-compute Fibonacci sphere ray directions (uniform sampling on spherical cap) +const _fibLidarDirs = (() => { + const golden = (1 + Math.sqrt(5)) / 2; + const zMin = Math.sin(LIDAR_V_MIN_RAD); // sin(-7°) ≈ -0.122 + const zMax = Math.sin(LIDAR_V_MAX_RAD); // sin(52°) ≈ 0.788 + const dirs = new Float32Array(LIDAR_NUM_POINTS * 3); + for (let i = 0; i < LIDAR_NUM_POINTS; i++) { + const z = zMin + (zMax - zMin) * (i + 0.5) / LIDAR_NUM_POINTS; + const r = Math.sqrt(1 - z * z); + const phi = 2 * Math.PI * i / golden; + dirs[i * 3 + 0] = r * Math.cos(phi); // x (forward in FLU) + dirs[i * 3 + 1] = r * Math.sin(phi); // y (left in FLU) + dirs[i * 3 + 2] = z; // z (up in FLU) + } + return dirs; +})(); +const LIDAR_ACCUM_FRAMES = 50; +const LIDAR_STATS_INTERVAL_MS = 1500; +const LIDAR_ACCUM_MIN_TRANSLATION_M = 0.08; +const LIDAR_ACCUM_MIN_ROT_DEG = 1.5; +const LIDAR_ACCUM_REFRESH_S = 2.0; + +// Lidar frame uses FLU convention: +// x=forward, y=left, z=up (right-handed). Camera local is x=right, y=up, z=back. +const _lidarToCamQuat = (() => { + const m = new THREE.Matrix4().set( + 0, -1, 0, 0, + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 0, 0, 1 + ); + return new THREE.Quaternion().setFromRotationMatrix(m); +})(); + +// Pose history for deskew (camera used as lidar pose proxy) +const _lidarPoseHistory = []; // [{stampNs, pos:Vector3, quat:Quaternion}] +const LIDAR_POSE_HISTORY_NS = 2_000_000_000; // keep ~2s history +let _lidarLastStatsMs = 0; +let _lidarUseKnownGoodDebugCloud = false; + +function nowNs() { + // Use unix epoch in ns consistently (browser clock based). + return Math.floor(performance.timeOrigin * 1e6 + performance.now() * 1e6); +} + +function pushLidarPoseSample(stampNs = nowNs()) { + let pos, quat; + const dimosAgent = dimosMode && window.__dimosAgent; + if (dimosAgent) { + // In dimos mode, sample from the agent's body position + orientation. + // getPosition() returns capsule center (~0.37m above ground), so subtract + // capsule half-extent to get ground level, then add mount height. + const [ax, ay, az] = dimosAgent.getPosition?.() || [0, 0, 0]; + const groundY = ay - (PLAYER_HALF_HEIGHT + PLAYER_RADIUS); + const lidarY = groundY + LIDAR_MOUNT_HEIGHT; + pos = new THREE.Vector3(ax, lidarY, az); + const yaw = window.__dimosYaw ?? dimosAgent.group?.rotation?.y ?? 0; + const agentQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yaw); + quat = agentQuat.multiply(_lidarToCamQuat); + } else { + pos = camera.getWorldPosition(new THREE.Vector3()); + const camQuat = camera.getWorldQuaternion(new THREE.Quaternion()); + quat = camQuat.clone().multiply(_lidarToCamQuat); + } + _lidarPoseHistory.push({ stampNs, pos, quat }); + const minNs = stampNs - LIDAR_POSE_HISTORY_NS; + while (_lidarPoseHistory.length > 2 && _lidarPoseHistory[0].stampNs < minNs) { + _lidarPoseHistory.shift(); + } +} + +function getLidarPoseAtNs(stampNs) { + if (_lidarPoseHistory.length === 0) { + const camQuat = camera.getWorldQuaternion(new THREE.Quaternion()); + return { + pos: camera.getWorldPosition(new THREE.Vector3()), + quat: camQuat.multiply(_lidarToCamQuat), + }; + } + if (_lidarPoseHistory.length === 1) { + return { + pos: _lidarPoseHistory[0].pos.clone(), + quat: _lidarPoseHistory[0].quat.clone(), + }; + } + // Find bounding samples + let i1 = 0; + while (i1 < _lidarPoseHistory.length && _lidarPoseHistory[i1].stampNs < stampNs) i1++; + if (i1 <= 0) { + return { + pos: _lidarPoseHistory[0].pos.clone(), + quat: _lidarPoseHistory[0].quat.clone(), + }; + } + if (i1 >= _lidarPoseHistory.length) { + const last = _lidarPoseHistory[_lidarPoseHistory.length - 1]; + return { pos: last.pos.clone(), quat: last.quat.clone() }; + } + const a = _lidarPoseHistory[i1 - 1]; + const b = _lidarPoseHistory[i1]; + const alpha = (stampNs - a.stampNs) / Math.max(1, b.stampNs - a.stampNs); + const pos = a.pos.clone().lerp(b.pos, alpha); + const quat = a.quat.clone().slerp(b.quat, alpha); + return { pos, quat }; +} + +function composeTwlFlat64(pos, quat) { + const m = new THREE.Matrix4().compose(pos, quat, new THREE.Vector3(1, 1, 1)); + const e = m.elements; + // Return row-major 4x4 flattened float64 (explicitly for stable downstream use) + return new Float64Array([ + e[0], e[4], e[8], e[12], + e[1], e[5], e[9], e[13], + e[2], e[6], e[10], e[14], + e[3], e[7], e[11], e[15], + ]); +} + +function twlInverseMatrix(pos, quat) { + const twl = new THREE.Matrix4().compose(pos, quat, new THREE.Vector3(1, 1, 1)); + return twl.clone().invert(); +} + +function lidarVerticalAngleForRing(ring) { + if (LIDAR_NUM_RINGS === 1) return 0; + const t = ring / (LIDAR_NUM_RINGS - 1); + return LIDAR_V_MIN_RAD + (LIDAR_V_MAX_RAD - LIDAR_V_MIN_RAD) * t; +} + +function makeRoboValLidarFrame({ + frameId, + stampNs, + points, + intensity, + ring, + t, + hasRing, + hasPerPointTime, + scanDurationS, + poseTWorldLidar, +}) { + // RoboValLidarFrame schema (used across sim/export/eval) + return { + frame_id: frameId, + stamp_ns: stampNs, + points, // Float32Array length N*3 (xyz meters, lidar frame) + intensity, // Float32Array length N + ring, // Uint16Array length N + t, // Float32Array length N (seconds from start of scan) + has_ring: hasRing, + has_per_point_time: hasPerPointTime, + scan_duration_s: scanDurationS, + pose_T_world_lidar: poseTWorldLidar, // Float64Array length 16, row-major + }; +} + +// ROS2 PointField datatype constants: +// INT8=1, UINT8=2, INT16=3, UINT16=4, INT32=5, UINT32=6, FLOAT32=7, FLOAT64=8 +function to_pointcloud2(frame) { + const n = Math.floor((frame.points?.length || 0) / 3); + const pointStep = 22; // x,y,z,float32(12) + intensity,float32(4) + ring,uint16(2) + t,float32(4) + const data = new Uint8Array(n * pointStep); + const dv = new DataView(data.buffer); + for (let i = 0; i < n; i++) { + const o = i * pointStep; + dv.setFloat32(o + 0, frame.points[i * 3 + 0], true); + dv.setFloat32(o + 4, frame.points[i * 3 + 1], true); + dv.setFloat32(o + 8, frame.points[i * 3 + 2], true); + dv.setFloat32(o + 12, frame.intensity[i] ?? 0, true); + dv.setUint16(o + 16, frame.ring[i] ?? 0, true); + dv.setFloat32(o + 18, frame.t[i] ?? 0, true); + } + return { + header: { + frame_id: frame.frame_id, + stamp: { + sec: Math.floor(frame.stamp_ns / 1e9), + nanosec: Math.floor(frame.stamp_ns % 1e9), + }, + }, + height: 1, + width: n, + fields: [ + { name: "x", offset: 0, datatype: 7, count: 1 }, + { name: "y", offset: 4, datatype: 7, count: 1 }, + { name: "z", offset: 8, datatype: 7, count: 1 }, + { name: "intensity", offset: 12, datatype: 7, count: 1 }, + { name: "ring", offset: 16, datatype: 4, count: 1 }, + { name: "t", offset: 18, datatype: 7, count: 1 }, + ], + is_bigendian: false, + point_step: pointStep, + row_step: pointStep * n, + data, + is_dense: true, + }; +} + +function toNpyBytes(typedArray, shape, descr) { + // NPY v1.0 + const magic = new Uint8Array([0x93, 0x4e, 0x55, 0x4d, 0x50, 0x59, 0x01, 0x00]); + const shapeStr = `(${shape.join(", ")}${shape.length === 1 ? "," : ""})`; + let header = `{'descr': '${descr}', 'fortran_order': False, 'shape': ${shapeStr}, }`; + // Pad so (magic+2-byte-len+header+\n) % 16 == 0 + const preamble = 10; + const base = preamble + header.length + 1; + const pad = (16 - (base % 16)) % 16; + header = header + " ".repeat(pad) + "\n"; + const headerBytes = new TextEncoder().encode(header); + const out = new Uint8Array(magic.length + 2 + headerBytes.length + typedArray.byteLength); + out.set(magic, 0); + const dv = new DataView(out.buffer); + dv.setUint16(magic.length, headerBytes.length, true); + out.set(headerBytes, magic.length + 2); + out.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), magic.length + 2 + headerBytes.length); + return out; +} + +function makeZipStore(entries) { + // Uncompressed ZIP (store) writer for deterministic byte output ordering. + const enc = new TextEncoder(); + const localParts = []; + const centralParts = []; + let offset = 0; + const files = []; + const crcTable = (() => { + const t = new Uint32Array(256); + for (let i = 0; i < 256; i++) { + let c = i; + for (let k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); + t[i] = c >>> 0; + } + return t; + })(); + const crc32 = (u8) => { + let c = 0xffffffff; + for (let i = 0; i < u8.length; i++) c = crcTable[(c ^ u8[i]) & 0xff] ^ (c >>> 8); + return (c ^ 0xffffffff) >>> 0; + }; + + for (const e of entries) { + const nameBytes = enc.encode(e.name); + const data = e.data; + const crc = crc32(data); + const lfh = new Uint8Array(30 + nameBytes.length); + const dv = new DataView(lfh.buffer); + dv.setUint32(0, 0x04034b50, true); + dv.setUint16(4, 20, true); + dv.setUint16(6, 0, true); + dv.setUint16(8, 0, true); // store + dv.setUint16(10, 0, true); + dv.setUint16(12, 0, true); + dv.setUint32(14, crc, true); + dv.setUint32(18, data.length, true); + dv.setUint32(22, data.length, true); + dv.setUint16(26, nameBytes.length, true); + dv.setUint16(28, 0, true); + lfh.set(nameBytes, 30); + localParts.push(lfh, data); + files.push({ nameBytes, crc, size: data.length, offset }); + offset += lfh.length + data.length; + } + + let centralSize = 0; + for (const f of files) { + const cfh = new Uint8Array(46 + f.nameBytes.length); + const dv = new DataView(cfh.buffer); + dv.setUint32(0, 0x02014b50, true); + dv.setUint16(4, 20, true); + dv.setUint16(6, 20, true); + dv.setUint16(8, 0, true); + dv.setUint16(10, 0, true); + dv.setUint16(12, 0, true); + dv.setUint16(14, 0, true); + dv.setUint32(16, f.crc, true); + dv.setUint32(20, f.size, true); + dv.setUint32(24, f.size, true); + dv.setUint16(28, f.nameBytes.length, true); + dv.setUint16(30, 0, true); + dv.setUint16(32, 0, true); + dv.setUint16(34, 0, true); + dv.setUint16(36, 0, true); + dv.setUint32(38, 0, true); + dv.setUint32(42, f.offset, true); + cfh.set(f.nameBytes, 46); + centralParts.push(cfh); + centralSize += cfh.length; + } + + const eocd = new Uint8Array(22); + const dvE = new DataView(eocd.buffer); + dvE.setUint32(0, 0x06054b50, true); + dvE.setUint16(4, 0, true); + dvE.setUint16(6, 0, true); + dvE.setUint16(8, files.length, true); + dvE.setUint16(10, files.length, true); + dvE.setUint32(12, centralSize, true); + dvE.setUint32(16, offset, true); + dvE.setUint16(20, 0, true); + + return new Blob([...localParts, ...centralParts, eocd], { type: "application/zip" }); +} + +function frameToNpzBlob(frame, rangeImage = null) { + const n = Math.floor((frame.points?.length || 0) / 3); + const xyz = toNpyBytes(frame.points, [n, 3], " URL.revokeObjectURL(a1.href), 500); + + const a2 = document.createElement("a"); + a2.href = URL.createObjectURL(deskBlob); + a2.download = `${base}_lidar_deskewed.npz`; + document.body.appendChild(a2); + a2.click(); + a2.remove(); + setTimeout(() => URL.revokeObjectURL(a2.href), 500); + + if (rangeImage) { + const rBlob = makeZipStore([ + { name: "range.npy", data: toNpyBytes(rangeImage.range, [rangeImage.H, rangeImage.W], " URL.revokeObjectURL(a3.href), 500); + } +} + +const lidarVizGroup = new THREE.Group(); +lidarVizGroup.name = "lidarVizGroup"; +lidarVizGroup.visible = false; +const LIDAR_VIZ_MAX_POINTS = LIDAR_MAX_POINTS * LIDAR_ACCUM_FRAMES; +const _lidarPosArray = new Float32Array(LIDAR_VIZ_MAX_POINTS * 3); +const _lidarColArray = new Float32Array(LIDAR_VIZ_MAX_POINTS * 3); +const _lidarAccumFrames = []; // [{pos: Float32Array, col: Float32Array}] +let _lidarLastAccumPose = null; // {pos:Vector3, quat:Quaternion, stampNs:number} +const _lidarGeom = new THREE.BufferGeometry(); +_lidarGeom.setAttribute("position", new THREE.BufferAttribute(_lidarPosArray, 3)); +_lidarGeom.setAttribute("color", new THREE.BufferAttribute(_lidarColArray, 3)); +_lidarGeom.setDrawRange(0, 0); +const _lidarMat = new THREE.PointsMaterial({ + color: 0xffffff, + vertexColors: true, + size: 0.03, + sizeAttenuation: true, + depthTest: true, + transparent: false, +}); +const _lidarPoints = new THREE.Points(_lidarGeom, _lidarMat); +_lidarPoints.frustumCulled = false; // point cloud covers entire scene; never cull +console.assert(_lidarPoints.isPoints === true, "[LiDAR] Visualization must use THREE.Points"); +lidarVizGroup.add(_lidarPoints); +scene.add(lidarVizGroup); +let _lidarLastNonZeroDrawCount = 0; +const rgbdPcOverlayGroup = new THREE.Group(); +rgbdPcOverlayGroup.name = "rgbdPcOverlayGroup"; +rgbdPcOverlayGroup.visible = false; +const RGBD_PC_OVERLAY_MAX_POINTS = 12000; +const RGBD_PC_OVERLAY_MIN_TRANSLATION_M = 0.15; +const RGBD_PC_OVERLAY_MIN_ROT_DEG = 4.0; +const _rgbdPcPosArray = new Float32Array(RGBD_PC_OVERLAY_MAX_POINTS * 3); +const _rgbdPcColArray = new Float32Array(RGBD_PC_OVERLAY_MAX_POINTS * 3); +const _rgbdPcGeom = new THREE.BufferGeometry(); +_rgbdPcGeom.setAttribute("position", new THREE.BufferAttribute(_rgbdPcPosArray, 3)); +_rgbdPcGeom.setAttribute("color", new THREE.BufferAttribute(_rgbdPcColArray, 3)); +_rgbdPcGeom.setDrawRange(0, 0); +const _rgbdPcMat = new THREE.PointsMaterial({ + color: 0x00ff4f, + vertexColors: true, + size: 3.0, + sizeAttenuation: false, + depthTest: false, + depthWrite: false, + blending: THREE.AdditiveBlending, + transparent: true, + opacity: 1.0, +}); +const _rgbdPcPoints = new THREE.Points(_rgbdPcGeom, _rgbdPcMat); +_rgbdPcPoints.frustumCulled = false; // overlay covers entire scene; never cull +console.assert(_rgbdPcPoints.isPoints === true, "[RGB-D overlay] Visualization must use THREE.Points"); +_rgbdPcPoints.renderOrder = 2000; +rgbdPcOverlayGroup.add(_rgbdPcPoints); +scene.add(rgbdPcOverlayGroup); +let _rgbdPcOverlayLastUpdateMs = 0; +let _rgbdPcOverlayLastPose = null; +let _rgbdPcOverlayLastCount = 0; +let _rgbdPcOverlayDirty = false; + +let _lidarScanState = null; // incremental scan state (processed across frames) + +function updateSimSensorButtons() { + if (simViewCompareBtn) simViewCompareBtn.classList.toggle("active", simCompareView); + if (simViewRgbdBtn) simViewRgbdBtn.classList.toggle("active", simSensorViewMode === "rgbd" && !simCompareView); + if (simRgbdGrayBtn) simRgbdGrayBtn.classList.toggle("active", rgbdVizMode === "gray"); + if (simRgbdColormapBtn) simRgbdColormapBtn.classList.toggle("active", rgbdVizMode === "colormap"); + if (simRgbdAutoRangeBtn) simRgbdAutoRangeBtn.classList.toggle("active", rgbdAutoRange); + if (simRgbdNoiseBtn) simRgbdNoiseBtn.classList.toggle("active", rgbdNoiseEnabled); + if (simRgbdSpeckleBtn) simRgbdSpeckleBtn.classList.toggle("active", rgbdSpeckleEnabled); + if (simRgbdPcOverlayBtn) simRgbdPcOverlayBtn.classList.toggle("active", rgbdPcOverlayOnLidar); + if (simRgbdMinEl) simRgbdMinEl.disabled = rgbdAutoRange; + if (simRgbdMaxEl) simRgbdMaxEl.disabled = rgbdAutoRange; + if (simViewLidarBtn) simViewLidarBtn.classList.toggle("active", simSensorViewMode === "lidar" && !lidarOrderedDebugView && !simCompareView); + if (simLidarColorRangeBtn) simLidarColorRangeBtn.classList.toggle("active", lidarColorByRange); + if (simLidarOrderedDebugBtn) simLidarOrderedDebugBtn.classList.toggle("active", lidarOrderedDebugView); + if (simLidarNoiseBtn) simLidarNoiseBtn.classList.toggle("active", lidarNoiseEnabled); + if (simLidarMultiReturnBtn) { + simLidarMultiReturnBtn.classList.toggle("active", lidarMultiReturnMode === "last"); + simLidarMultiReturnBtn.textContent = lidarMultiReturnMode === "last" ? "LiDAR: Last Return" : "LiDAR: Strongest"; + } + updateRgbdRangeLabels(); +} + +function applySimPanelCollapsedState() { + if (!overlayEl || !agentPanelEl) return; + const shouldCollapse = simPanelCollapsed; + overlayEl.classList.toggle("sim-panel-collapsed", shouldCollapse); + agentPanelEl.classList.toggle("hidden", shouldCollapse); + simPanelOpenBtn?.classList.toggle("hidden", !shouldCollapse); +} + +function lidarRangeColor01(t) { + // Deterministic near->far gradient: cyan -> green -> yellow -> red + const x = Math.min(1, Math.max(0, t)); + if (x < 0.33) { + const u = x / 0.33; + return [0.05 + 0.35 * u, 0.98, 0.98 - 0.88 * u]; + } + if (x < 0.66) { + const u = (x - 0.33) / 0.33; + return [0.40 + 0.58 * u, 0.95 - 0.15 * u, 0.10 * (1.0 - u)]; + } + const u = (x - 0.66) / 0.34; + return [0.98, 0.80 - 0.65 * u, 0.02 + 0.03 * (1.0 - u)]; +} + +function lidarHash01(seed) { + let x = seed | 0; + x ^= x >>> 16; + x = Math.imul(x, 0x7feb352d); + x ^= x >>> 15; + x = Math.imul(x, 0x846ca68b); + x ^= x >>> 16; + return (x >>> 0) / 4294967296; +} + +function lidarGaussianNoise(seedBase) { + // Deterministic approx N(0,1) from 6 uniforms (CLT). + let s = 0; + for (let i = 0; i < 6; i++) { + s += lidarHash01(seedBase + i * 2654435761); + } + return s - 3.0; +} + +function applyLidarRealityModel(toi, incidence, scanSeed, vi, hi) { + let outRange = toi; + let dropped = false; + + if (lidarNoiseEnabled) { + // Indoor-friendly deterministic noise profile (meters). + const sigma = 0.004 + 0.0015 * Math.max(0, toi); // ~4mm near, grows with range + const n = lidarGaussianNoise(scanSeed ^ (vi * 73856093) ^ (hi * 19349663)); + outRange = Math.max(LIDAR_MIN_RANGE_M, Math.min(LIDAR_MAX_RANGE_M, outRange + sigma * n)); + + const tr = Math.min(1, Math.max(0, toi / LIDAR_MAX_RANGE_M)); + const dropoutP = 0.005 + 0.04 * tr * tr; // deterministic, stronger at longer range + const u = lidarHash01(scanSeed ^ (vi * 83492791) ^ (hi * 2654435761)); + if (u < dropoutP) dropped = true; + } + + // Multi-return knob for future lidar profiles. + // With a single physics hit, "last" is approximated as a slight farther-biased return. + if (!dropped && lidarMultiReturnMode === "last") { + const weakSurface = 1.0 - Math.max(0, Math.min(1, incidence)); + const tail = 0.015 * weakSurface; // up to 1.5 cm + outRange = Math.min(LIDAR_MAX_RANGE_M, outRange + tail); + } + + return { range: outRange, dropped }; +} + +function buildKnownGoodDebugCloud() { + // Deterministic 1m cube grid centered 2m in front of camera. + const center = new THREE.Vector3(0, 0, -2).applyMatrix4(camera.matrixWorld); + const step = 0.1; // 11^3 ~= 1331 points + const points = []; + const colors = []; + for (let x = -0.5; x <= 0.5001; x += step) { + for (let y = -0.5; y <= 0.5001; y += step) { + for (let z = -0.5; z <= 0.5001; z += step) { + points.push(center.x + x, center.y + y, center.z + z); + colors.push(0.15 + (x + 0.5) * 0.7, 0.25 + (y + 0.5) * 0.6, 0.95 - (z + 0.5) * 0.5); + } + } + } + return { + pos: new Float32Array(points), + col: new Float32Array(colors), + }; +} + +function logLidarFrameStats(points, n, ring) { + const now = performance.now(); + if (now - _lidarLastStatsMs < LIDAR_STATS_INTERVAL_MS) return; + _lidarLastStatsMs = now; + if (!n) { + console.info("[LiDAR stats]", { n_points: 0, nan_inf_pct: 0 }); + return; + } + let minX = Infinity, minY = Infinity, minZ = Infinity; + let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity; + let ringMin = Infinity; + let ringMax = -Infinity; + let bad = 0; + const yQuant = new Set(); + for (let i = 0; i < n; i++) { + const x = points[i * 3 + 0]; + const y = points[i * 3 + 1]; + const z = points[i * 3 + 2]; + if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) { + bad++; + continue; + } + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + if (z < minZ) minZ = z; + if (z > maxZ) maxZ = z; + yQuant.add(Math.round(y * 1000)); + const rr = ring[i]; + if (rr < ringMin) ringMin = rr; + if (rr > ringMax) ringMax = rr; + } + console.info("[LiDAR stats]", { + n_points: n, + min: { x: minX, y: minY, z: minZ }, + max: { x: maxX, y: maxY, z: maxZ }, + nan_inf_pct: (100 * bad) / n, + unique_y_mm: yQuant.size, + rings_configured: LIDAR_NUM_RINGS, + ring_min: Number.isFinite(ringMin) ? ringMin : 0, + ring_max: Number.isFinite(ringMax) ? ringMax : 0, + }); +} + +function shouldAppendAccumFrame(refPose, stampNs) { + if (!_lidarLastAccumPose) return true; + const dtS = (stampNs - _lidarLastAccumPose.stampNs) / 1e9; + if (dtS >= LIDAR_ACCUM_REFRESH_S) return true; + const dp = refPose.pos.distanceTo(_lidarLastAccumPose.pos); + if (dp >= LIDAR_ACCUM_MIN_TRANSLATION_M) return true; + const ang = THREE.MathUtils.radToDeg(refPose.quat.angleTo(_lidarLastAccumPose.quat)); + if (ang >= LIDAR_ACCUM_MIN_ROT_DEG) return true; + return false; +} + +function resetLidarScanState() { + _lidarScanState = null; +} + +function updateLidarPointCloud() { + if (!rapierWorld || !RAPIER || (simSensorViewMode !== "lidar" && !dimosMode)) return; + + if (_lidarUseKnownGoodDebugCloud) { + resetLidarScanState(); + const dbg = buildKnownGoodDebugCloud(); + const nDbg = Math.min(LIDAR_VIZ_MAX_POINTS, Math.floor(dbg.pos.length / 3)); + _lidarPosArray.set(dbg.pos.subarray(0, nDbg * 3), 0); + _lidarColArray.set(dbg.col.subarray(0, nDbg * 3), 0); + _lidarGeom.setDrawRange(0, nDbg); + _lidarGeom.attributes.position.needsUpdate = true; + _lidarGeom.attributes.color.needsUpdate = true; + lidarVizGroup.position.set(0, 0, 0); + lidarVizGroup.quaternion.identity(); + lidarVizGroup.scale.set(1, 1, 1); + return; + } + + // Build set of collider handles to exclude from lidar raycasts. + // Excludes player collider and ALL AI agent colliders (lidar origin is inside them). + // In dimos mode, also explicitly exclude the active dimos agent body/colliders. + const _lidarExcludeHandles = new Set(); + const _lidarHostAgent = dimosMode ? window.__dimosAgent : null; + const _lidarExcludeRigidBodyHandle = _lidarHostAgent?.body?.handle; + if (playerCollider) _lidarExcludeHandles.add(playerCollider.handle); + if (_lidarHostAgent?.collider?.handle != null) _lidarExcludeHandles.add(_lidarHostAgent.collider.handle); + if (_lidarHostAgent?.spineCollider?.handle != null) _lidarExcludeHandles.add(_lidarHostAgent.spineCollider.handle); + for (const a of aiAgents) { + if (a?.collider) _lidarExcludeHandles.add(a.collider.handle); + if (a?.spineCollider) _lidarExcludeHandles.add(a.spineCollider.handle); + } + + // Livox Mid-360 style: Fibonacci sphere sampling, incremental over ~0.1s wall-clock. + const N = LIDAR_NUM_POINTS; + const scanDurationS = LIDAR_SCAN_DURATION_S; + const scanDurationNs = Math.floor(scanDurationS * 1e9); + if (!_lidarScanState) { + const scanStartNs = nowNs(); + _lidarScanCount++; + const jitterAngle = _lidarScanCount * 2.399963; // golden angle rotation per scan + const rangeImg = new Float32Array(LIDAR_NUM_RINGS * LIDAR_RANGE_IMAGE_W); + const intenImg = new Float32Array(LIDAR_NUM_RINGS * LIDAR_RANGE_IMAGE_W); + const ringIdxImg = new Uint16Array(LIDAR_NUM_RINGS * LIDAR_RANGE_IMAGE_W); + _lidarScanState = { + scanStartNs, + scanDurationS, + scanDurationNs, + scanSeed: (scanStartNs / 1e6) | 0, + cosJitter: Math.cos(jitterAngle), + sinJitter: Math.sin(jitterAngle), + nextIdx: 0, + n: 0, + rawPts: new Float32Array(LIDAR_MAX_POINTS * 3), + deskPts: new Float32Array(LIDAR_MAX_POINTS * 3), + intensity: new Float32Array(LIDAR_MAX_POINTS), + ring: new Uint16Array(LIDAR_MAX_POINTS), + tArr: new Float32Array(LIDAR_MAX_POINTS), + worldPts: new Float32Array(LIDAR_MAX_POINTS * 3), + colArray: new Float32Array(LIDAR_MAX_POINTS * 3), + rangeImg, + intenImg, + ringIdxImg, + }; + } + const st = _lidarScanState; + const dirLocal = new THREE.Vector3(); + const dirWorld = new THREE.Vector3(); + const pWorld = new THREE.Vector3(); + const pRawLocal = new THREE.Vector3(); + const elapsedNs = Math.max(0, nowNs() - st.scanStartNs); + const progress = Math.min(1, elapsedNs / Math.max(1, st.scanDurationNs)); + let targetIdx = Math.floor(progress * N); + targetIdx = Math.max(targetIdx, Math.min(N, st.nextIdx + 1)); + if (elapsedNs >= st.scanDurationNs) targetIdx = N; + + const cosJ = st.cosJitter, sinJ = st.sinJitter; + + for (let i = st.nextIdx; i < targetIdx; i++) { + { + if (st.n >= LIDAR_MAX_POINTS) break; + // Fibonacci direction with per-scan golden-angle rotation around Z (non-repetitive) + const fx = _fibLidarDirs[i * 3 + 0], fy = _fibLidarDirs[i * 3 + 1], fz = _fibLidarDirs[i * 3 + 2]; + const tSec = (i / Math.max(1, N - 1)) * scanDurationS; + const stampNs = st.scanStartNs + Math.floor(tSec * 1e9); + const pose = getLidarPoseAtNs(stampNs); + const w2lNow = twlInverseMatrix(pose.pos, pose.quat); + const origin = pose.pos; + + // Fibonacci direction rotated by per-scan golden angle (FLU frame) + dirLocal.set(fx * cosJ - fy * sinJ, fx * sinJ + fy * cosJ, fz); + dirWorld.copy(dirLocal).applyQuaternion(pose.quat).normalize(); + const ray = new RAPIER.Ray( + { x: origin.x, y: origin.y, z: origin.z }, + { x: dirWorld.x, y: dirWorld.y, z: dirWorld.z } + ); + let hit = null; + let singleExcludeHandle = undefined; + // Defensive retry: if a self-collider slips through, recast while excluding it. + // Keeps scans alive even if exclusion bookkeeping is briefly stale. + for (let castAttempt = 0; castAttempt < 4; castAttempt++) { + hit = rapierWorld.queryPipeline.castRayAndGetNormal( + rapierWorld.bodies, + rapierWorld.colliders, + ray, + LIDAR_MAX_RANGE_M, + false, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + singleExcludeHandle, + _lidarExcludeRigidBodyHandle, + (h) => !_lidarExcludeHandles.has(h) + ); + const hitHandle = hit?.colliderHandle; + if (!hit || hitHandle == null || !_lidarExcludeHandles.has(hitHandle)) break; + singleExcludeHandle = hitHandle; + } + let toi = hit ? (hit.toi ?? hit.timeOfImpact ?? 0) : Infinity; + const hitNormal = hit?.normal || null; + + // Ground-truth-style lidar: no-return beams are omitted. + if (!Number.isFinite(toi) || toi > LIDAR_MAX_RANGE_M || toi < LIDAR_MIN_RANGE_M) continue; + + const nx = hitNormal?.x ?? 0; + const ny = hitNormal?.y ?? 0; + const nz = hitNormal?.z ?? 1; + const incidence = hitNormal ? Math.max(0, -(dirWorld.x * nx + dirWorld.y * ny + dirWorld.z * nz)) : 0.7; + const reality = applyLidarRealityModel(toi, incidence, st.scanSeed, i & 0xff, i >> 8); + if (reality.dropped) continue; + toi = reality.range; + + pWorld.set( + origin.x + dirWorld.x * toi, + origin.y + dirWorld.y * toi, + origin.z + dirWorld.z * toi + ); + pRawLocal.copy(pWorld).applyMatrix4(w2lNow); + + st.rawPts[st.n * 3 + 0] = pRawLocal.x; + st.rawPts[st.n * 3 + 1] = pRawLocal.y; + st.rawPts[st.n * 3 + 2] = pRawLocal.z; + st.worldPts[st.n * 3 + 0] = pWorld.x; + st.worldPts[st.n * 3 + 1] = pWorld.y; + st.worldPts[st.n * 3 + 2] = pWorld.z; + + st.ring[st.n] = 0; + st.tArr[st.n] = tSec; + + const atten = 1.0 / (1.0 + 0.02 * toi * toi); + const I = Math.max(0.06, Math.min(1.0, incidence * atten)); + st.intensity[st.n] = I; + const tr = Math.min(1, Math.max(0, toi / LIDAR_MAX_RANGE_M)); + const depthShade = 1.0 - 0.35 * tr; // cheap EDL-like darkening by depth/range + + if (lidarColorByRange) { + const [r, g, b] = lidarRangeColor01(tr); + st.colArray[st.n * 3 + 0] = r * depthShade; + st.colArray[st.n * 3 + 1] = g * depthShade; + st.colArray[st.n * 3 + 2] = b * depthShade; + } else { + // Intensity-like grayscale (closer to raw LiDAR semantics) + const g = I * depthShade; + st.colArray[st.n * 3 + 0] = g; + st.colArray[st.n * 3 + 1] = g; + st.colArray[st.n * 3 + 2] = g; + } + st.n++; + } + } + st.nextIdx = targetIdx; + if (st.nextIdx < N) { + // Keep LiDAR visible while a scan is still being built. + // If we don't have accumulated frames yet, show the partial current scan. + if (!lidarOrderedDebugView && _lidarAccumFrames.length === 0 && st.n > 0) { + _lidarPosArray.set(st.worldPts.subarray(0, st.n * 3), 0); + _lidarColArray.set(st.colArray.subarray(0, st.n * 3), 0); + _lidarGeom.setDrawRange(0, st.n); + if (st.n > 0) _lidarLastNonZeroDrawCount = st.n; + _lidarGeom.attributes.position.needsUpdate = true; + _lidarGeom.attributes.color.needsUpdate = true; + lidarVizGroup.position.set(0, 0, 0); + lidarVizGroup.quaternion.identity(); + lidarVizGroup.scale.set(1, 1, 1); + } + return; // scan still in progress + } + + const scanEndNs = st.scanStartNs + st.scanDurationNs; + const refPose = getLidarPoseAtNs(scanEndNs); + const refTwlFlat = composeTwlFlat64(refPose.pos, refPose.quat); + const refW2L = twlInverseMatrix(refPose.pos, refPose.quat); + const pDeskLocal = new THREE.Vector3(); + for (let i = 0; i < st.n; i++) { + pDeskLocal.set( + st.worldPts[i * 3 + 0], + st.worldPts[i * 3 + 1], + st.worldPts[i * 3 + 2] + ).applyMatrix4(refW2L); + st.deskPts[i * 3 + 0] = pDeskLocal.x; + st.deskPts[i * 3 + 1] = pDeskLocal.y; + st.deskPts[i * 3 + 2] = pDeskLocal.z; + } + + logLidarFrameStats(st.worldPts, st.n, st.ring); + + const rawFrame = makeRoboValLidarFrame({ + frameId: "lidar", + stampNs: scanEndNs, + points: st.rawPts.subarray(0, st.n * 3), + intensity: st.intensity.subarray(0, st.n), + ring: st.ring.subarray(0, st.n), + t: st.tArr.subarray(0, st.n), + hasRing: true, + hasPerPointTime: true, + scanDurationS, + poseTWorldLidar: refTwlFlat, + }); + const deskewedFrame = makeRoboValLidarFrame({ + frameId: "lidar", + stampNs: scanEndNs, + points: st.deskPts.subarray(0, st.n * 3), + intensity: st.intensity.subarray(0, st.n), + ring: st.ring.subarray(0, st.n), + t: st.tArr.subarray(0, st.n), + hasRing: true, + hasPerPointTime: true, + scanDurationS, + poseTWorldLidar: refTwlFlat, + }); + const sensorModelMeta = { + range_min_m: LIDAR_MIN_RANGE_M, + range_max_m: LIDAR_MAX_RANGE_M, + noise_enabled: lidarNoiseEnabled, + multi_return_mode: lidarMultiReturnMode, + ordered_render_debug: lidarOrderedDebugView, + deskewed: true, + }; + rawFrame.sensor_model = sensorModelMeta; + deskewedFrame.sensor_model = sensorModelMeta; + const rangeImage = { + H: LIDAR_NUM_RINGS, + W: LIDAR_RANGE_IMAGE_W, + range: st.rangeImg, + intensity: st.intenImg, + ring_index: st.ringIdxImg, + metadata: { + azimuth_convention: "col increases with azimuth in lidar FLU frame", + binning: "uniform azimuth bins", + num_rings: LIDAR_NUM_RINGS, + num_azimuth_bins: LIDAR_RANGE_IMAGE_W, + sensor_model: sensorModelMeta, + visualization_mode: lidarOrderedDebugView ? "single_sweep_ordered" : "accumulated_unordered", + accumulation: { + max_frames: LIDAR_ACCUM_FRAMES, + min_translation_m: LIDAR_ACCUM_MIN_TRANSLATION_M, + min_rotation_deg: LIDAR_ACCUM_MIN_ROT_DEG, + refresh_s: LIDAR_ACCUM_REFRESH_S, + }, + }, + }; + _lidarLatestRawFrame = rawFrame; + _lidarLatestDeskewedFrame = deskewedFrame; + _lidarLatestRangeImage = rangeImage; + // Save world-frame points for dimos bridge (Three.js Y-up coords). + // The bridge's cyclic permutation correctly converts these to ROS Z-up. + _lidarLatestWorldPts = st.worldPts.slice(0, st.n * 3); + _lidarLatestLocalPts = st.deskPts.slice(0, st.n * 3); + _lidarLatestWorldIntensity = st.intensity.slice(0, st.n); + + // Default visualization: accumulated world-space point cloud (depth-tested). + if (!lidarOrderedDebugView) { + if (shouldAppendAccumFrame(refPose, scanEndNs)) { + const framePos = new Float32Array(st.n * 3); + const frameCol = new Float32Array(st.n * 3); + framePos.set(st.worldPts.subarray(0, st.n * 3)); + frameCol.set(st.colArray.subarray(0, st.n * 3)); + _lidarAccumFrames.push({ pos: framePos, col: frameCol }); + while (_lidarAccumFrames.length > LIDAR_ACCUM_FRAMES) _lidarAccumFrames.shift(); + _lidarLastAccumPose = { + pos: refPose.pos.clone(), + quat: refPose.quat.clone(), + stampNs: scanEndNs, + }; + } + + let out = 0; + const len = _lidarAccumFrames.length; + for (let fi = 0; fi < len && out < LIDAR_VIZ_MAX_POINTS; fi++) { + const f = _lidarAccumFrames[fi]; + const age01 = len <= 1 ? 0 : (len - 1 - fi) / (len - 1); // 1 old -> 0 newest + const fade = 1.0 - 0.7 * age01; + const fn = Math.floor(f.pos.length / 3); + for (let i = 0; i < fn && out < LIDAR_VIZ_MAX_POINTS; i++, out++) { + _lidarPosArray[out * 3 + 0] = f.pos[i * 3 + 0]; + _lidarPosArray[out * 3 + 1] = f.pos[i * 3 + 1]; + _lidarPosArray[out * 3 + 2] = f.pos[i * 3 + 2]; + _lidarColArray[out * 3 + 0] = Math.max(0, Math.min(1, f.col[i * 3 + 0] * fade)); + _lidarColArray[out * 3 + 1] = Math.max(0, Math.min(1, f.col[i * 3 + 1] * fade)); + _lidarColArray[out * 3 + 2] = Math.max(0, Math.min(1, f.col[i * 3 + 2] * fade)); + } + } + if (out > 0) { + _lidarGeom.setDrawRange(0, out); + _lidarLastNonZeroDrawCount = out; + } + lidarVizGroup.position.set(0, 0, 0); + lidarVizGroup.quaternion.identity(); + lidarVizGroup.scale.set(1, 1, 1); + } else { + // Debug visualization: ordered current-frame cloud in deskewed lidar frame. + _lidarAccumFrames.length = 0; + _lidarPosArray.set(st.deskPts.subarray(0, st.n * 3), 0); + _lidarGeom.setDrawRange(0, st.n); + if (st.n > 0) _lidarLastNonZeroDrawCount = st.n; + lidarVizGroup.position.copy(refPose.pos); + lidarVizGroup.quaternion.copy(refPose.quat); + lidarVizGroup.scale.set(1, 1, 1); + } + _lidarGeom.attributes.position.needsUpdate = true; + _lidarGeom.attributes.color.needsUpdate = true; + // Guard against intermittent empty frames causing visible flicker. + if (_lidarGeom.drawRange.count <= 0 && _lidarLastNonZeroDrawCount > 0) { + _lidarGeom.setDrawRange(0, _lidarLastNonZeroDrawCount); + } + if (_lidarAutoExport) { + writeLidarFrameFiles(rawFrame, deskewedFrame, rangeImage); + } + resetLidarScanState(); +} + +function applySimSensorViewMode() { + if (simSensorViewMode === "rgb") { + // Restore default rendering. + scene.overrideMaterial = _savedOverrideMaterial; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + _rgbdPcGeom.setDrawRange(0, 0); + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + applySceneRgbBackground(); + } else if (simSensorViewMode === "rgbd") { + // RGB-D mode: render scene depth to offscreen target, then post-process to + // metric camera-space Z visualization. Do not override scene materials. + _savedOverrideMaterial = null; + scene.overrideMaterial = null; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + _rgbdPcGeom.setDrawRange(0, 0); + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + skyDome.visible = false; + scene.background = RGBD_BG; + } else { + // LiDAR mode: hide scene visuals and render deterministic point cloud only. + _savedOverrideMaterial = null; + scene.overrideMaterial = null; + assetsGroup.visible = false; + primitivesGroup.visible = false; + lightsGroup.visible = false; + tagsGroup.visible = false; + lidarVizGroup.visible = true; + rgbdPcOverlayGroup.visible = rgbdPcOverlayOnLidar && _rgbdPcOverlayLastCount > 0; + skyDome.visible = false; + scene.background = RGBD_BG; + } + updateSimSensorButtons(); +} + +function setSimSensorViewMode(mode) { + const next = mode === "rgbd" || mode === "lidar" ? mode : "rgb"; + // Toggle behavior: clicking an already-active sensor mode returns to RGB. + simSensorViewMode = (simSensorViewMode === next && next !== "rgb") ? "rgb" : next; + applySimSensorViewMode(); + if (simSensorViewMode === "rgb") { + setStatus("RGB view"); + } else if (simSensorViewMode === "rgbd") { + setStatus(`RGB-D ${rgbdVizMode === "gray" ? "grayscale" : "colormap"} (${rgbdRangeMinM.toFixed(1)}-${rgbdRangeMaxM.toFixed(1)}m)`); + } else { + setStatus(lidarOrderedDebugView ? "LiDAR single sweep view" : "LiDAR accumulated 3D point cloud"); + } +} + +// Controls: pointer-lock look + WASD move. +const controls = new PointerLockControls(camera, document.body); +scene.add(controls.object); + + +const keys = { + forward: false, + backward: false, + left: false, + right: false, + up: false, + down: false, +}; + +function setStatus(msg) { + if (statusEl) statusEl.textContent = msg || ""; + if (statusSimEl) statusSimEl.textContent = msg || ""; +} + +function randId() { + return Math.random().toString(16).slice(2) + "-" + Date.now().toString(16); +} + +function escapeHtml(s) { + return String(s) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function loadTagsForWorld() { + // Clean up old primitive colliders BEFORE replacing the arrays + for (const p of primitives) { + removePrimitiveCollider(p); + } + + try { + const rawState = localStorage.getItem("sparkWorldStateByWorld"); + const byWorld = rawState ? JSON.parse(rawState) : {}; + const state = byWorld[worldKey] || null; + if (state && typeof state === "object") { + tags = Array.isArray(state.tags) ? state.tags : []; + assets = Array.isArray(state.assets) ? state.assets.map(normalizeAsset).filter(Boolean) : []; + primitives = Array.isArray(state.primitives) ? state.primitives : []; + editorLights = Array.isArray(state.lights) ? state.lights : []; + groups = Array.isArray(state.groups) ? state.groups : []; + sceneSettings = normalizeSceneSettings(state.sceneSettings); + } else { + // Backwards compat: tags-only storage + const raw = localStorage.getItem("sparkWorldTagsByWorld"); + const byWorldOld = raw ? JSON.parse(raw) : {}; + tags = Array.isArray(byWorldOld[worldKey]) ? byWorldOld[worldKey] : []; + assets = []; + primitives = []; + editorLights = []; + groups = []; + sceneSettings = createDefaultSceneSettings(); + } + } catch { + tags = []; + assets = []; + primitives = []; + editorLights = []; + groups = []; + sceneSettings = createDefaultSceneSettings(); + } + selectedTagId = null; + draftTag = null; + selectedAssetId = null; + selectedPrimitiveId = null; + rebuildTagMarkers(); + renderTagsList(); + renderTagPanel(); + rebuildAssets(); + renderAssetsList(); + rebuildAllPrimitives(); + renderPrimitivesList(); + rebuildAllEditorLights(); + applySceneSkySettings(); + applySceneRgbBackground(); +} + +function saveTagsForWorld() { + try { + let rawState = localStorage.getItem("sparkWorldStateByWorld"); + let byWorld = {}; + + try { + byWorld = rawState ? JSON.parse(rawState) : {}; + } catch { + // Corrupted data, start fresh + console.warn("[SAVE] Corrupted localStorage data, clearing..."); + byWorld = {}; + } + + console.log(`[SAVE] Saving ${assets.length} assets for world: ${worldKey}`); + + // Only save lightweight metadata - NOT the full dataBase64 model data + // Only save state changes (currentStateId, transform) + const lightweightAssets = assets.map(a => { + // Regular assets: only save delta/metadata, not model data + return { + id: a.id, + currentStateId: a.currentStateId || a.currentState, + transform: a.transform, + pickable: a.pickable, + castShadow: a.castShadow ?? false, + receiveShadow: a.receiveShadow ?? false, + blobShadow: a.blobShadow || null, + _deltaOnly: true, + }; + }); + + // Save primitives — strip collider handles and large texture data URLs + // (textures are preserved in Export but too big for localStorage) + const savePrimitives = primitives.map((p) => { + const { _colliderHandle, ...rest } = p; + if (rest.material?.textureDataUrl) { + rest.material = { ...rest.material, textureDataUrl: null }; + } + return rest; + }); + + // Save lights (strip runtime objects) + const saveLights = editorLights.map((l) => { + const { _lightObj, _helperObj, _proxyObj, ...rest } = l; + return rest; + }); + + byWorld[worldKey] = { + tags, + assets: lightweightAssets, + primitives: savePrimitives, + lights: saveLights, + groups, + sceneSettings: serializeSceneSettings(), + }; + const dataStr = JSON.stringify(byWorld); + + // Check size before saving (localStorage limit is typically 5MB) + const sizeKB = (dataStr.length * 2) / 1024; // Rough estimate (UTF-16) + console.log(`[SAVE] Data size: ${sizeKB.toFixed(1)}KB`); + + localStorage.setItem("sparkWorldStateByWorld", dataStr); + localStorage.setItem("sparkWorldLastWorldKey", worldKey); + } catch (e) { + console.error("[SAVE] Failed to save world state:", e); + + // If quota exceeded, try clearing old data and retry + if (e.name === "QuotaExceededError") { + console.warn("[SAVE] Quota exceeded, clearing old world data..."); + try { + localStorage.removeItem("sparkWorldStateByWorld"); + // Retry with just current world and minimal data + const freshData = {}; + freshData[worldKey] = { + tags, + assets: [], + sceneSettings: serializeSceneSettings() + }; + localStorage.setItem("sparkWorldStateByWorld", JSON.stringify(freshData)); + console.log("[SAVE] Saved minimal data after clearing old data"); + } catch (e2) { + console.error("[SAVE] Still failed after clearing:", e2); + } + } + } +} + +// Clear all localStorage data for this app (useful for debugging) +function clearWorldStorage() { + localStorage.removeItem("sparkWorldStateByWorld"); + localStorage.removeItem("sparkWorldLastWorldKey"); + console.log("[STORAGE] Cleared all world storage"); +} +// Expose for debugging: window.clearWorldStorage = clearWorldStorage; + +function setWorldKey(key) { + worldKey = key || "default"; + localStorage.setItem("sparkWorldLastWorldKey", worldKey); + loadTagsForWorld(); +} + + +function getSelectedTag() { + return tags.find((t) => t.id === selectedTagId) ?? null; +} + +function renderTagPanel() { + // No-op in sim-only mode (editor tag panel not present) +} + +function renderTagsList() { + // No-op in sim-only mode (editor tags list not present) +} + +const markerGeom = new THREE.SphereGeometry(0.08, 12, 12); +const markerMat = new THREE.MeshBasicMaterial({ color: 0x7cc4ff }); +const markerMatActive = new THREE.MeshBasicMaterial({ color: 0xffd36e }); +const radiusGeom = new THREE.SphereGeometry(1, 20, 14); +const radiusMat = new THREE.MeshBasicMaterial({ + color: 0x7cc4ff, + transparent: true, + opacity: 0.08, + depthWrite: false, +}); + +function agentUiPush(event) { + const logs = [ + agentLogEl, + document.getElementById("edit-agent-log"), + ].filter(Boolean); + for (const log of logs) { + const el = document.createElement("div"); + el.className = "agent-log-item"; + el.textContent = event; + log.prepend(el); + // cap + while (log.children.length > 10) log.removeChild(log.lastChild); + } +} + +function agentUiSetLast(text) { + const value = text || ""; + if (agentLastEl) agentLastEl.textContent = value; + const editLast = document.getElementById("edit-agent-last"); + if (editLast) editLast.textContent = value; +} + +function agentUiSetShot(base64) { + if (!base64) return; + const src = `data:image/jpeg;base64,${base64}`; + if (agentShotImgEl) agentShotImgEl.src = src; + const editShot = document.getElementById("edit-agent-shot-img"); + if (editShot) editShot.src = src; +} + +function extractObservationText(parsed, raw) { + const p = parsed && typeof parsed === "object" ? parsed : {}; + const observation = + (typeof p.observation === "string" && p.observation) || + (typeof p.obs === "string" && p.obs) || + (typeof p.perception === "string" && p.perception) || + (typeof p.sceneObservation === "string" && p.sceneObservation) || + (typeof p.visualObservation === "string" && p.visualObservation) || + (typeof p.params?.observation === "string" && p.params.observation) || + ""; + if (observation.trim()) return observation.trim(); + + if (typeof raw === "string" && raw.trim()) { + const m = raw.match(/"observation"\s*:\s*"([^"]+)"/i); + if (m?.[1]) return m[1]; + } + return ""; +} + +function agentUiSetObservation(text) { + const value = String(text || "").trim(); + if (!agentObservationEl) return; + agentObservationEl.textContent = value || "No observation in latest response."; +} + +function agentUiSetRequest({ endpoint, model, prompt, context, imageBytes, messages }) { + const metaText = `endpoint: ${endpoint}\nmodel: ${model}\nimageBytes: ${imageBytes ?? "?"}\nworld: ${worldKey}`; + if (agentReqMetaEl) agentReqMetaEl.textContent = metaText; + const editMeta = document.getElementById("edit-agent-req-meta"); + if (editMeta) editMeta.textContent = metaText; + if (agentReqPromptEl) agentReqPromptEl.textContent = prompt || ""; + const editPrompt = document.getElementById("edit-agent-req-prompt"); + if (editPrompt) editPrompt.textContent = prompt || ""; + + // Format messages for display (only assistant and user messages, not system) + let contextText = ""; + if (messages && messages.length > 0) { + // Filter out system messages - only show assistant and user + const conversationMessages = messages.filter(msg => msg.role !== "system"); + if (conversationMessages.length > 0) { + contextText = conversationMessages.map((msg) => { + const role = msg.role.toUpperCase(); + let content = ""; + if (typeof msg.content === "string") { + content = msg.content; + } else if (Array.isArray(msg.content)) { + // Handle multimodal content (text + image) + content = msg.content.map(part => { + if (part.type === "text") return part.text; + if (part.type === "image_url") return "[IMAGE]"; + return JSON.stringify(part); + }).join("\n"); + } else { + content = JSON.stringify(msg.content, null, 2); + } + return `═══ ${role} ═══\n${content}`; + }).join("\n\n"); + } else { + contextText = "(No conversation history yet)"; + } + } else { + contextText = JSON.stringify(context ?? {}, null, 2); + } + if (agentReqContextEl) agentReqContextEl.textContent = contextText; + const editContext = document.getElementById("edit-agent-req-context"); + if (editContext) editContext.textContent = contextText; +} + +function agentUiSetResponse({ raw, parsed }) { + if (agentRespRawEl) agentRespRawEl.textContent = raw || ""; + const editRaw = document.getElementById("edit-agent-resp-raw"); + if (editRaw) editRaw.textContent = raw || ""; + if (agentLastEl) agentLastEl.textContent = JSON.stringify(parsed ?? {}, null, 2); + agentUiSetObservation(extractObservationText(parsed, raw)); + const editLast = document.getElementById("edit-agent-last"); + if (editLast) editLast.textContent = JSON.stringify(parsed ?? {}, null, 2); +} + +function clearAgentInspectorViews() { + if (agentShotImgEl) agentShotImgEl.removeAttribute("src"); + if (agentReqMetaEl) agentReqMetaEl.textContent = "No request yet"; + if (agentReqPromptEl) agentReqPromptEl.textContent = ""; + if (agentReqContextEl) agentReqContextEl.textContent = ""; + if (agentRespRawEl) agentRespRawEl.textContent = ""; + if (agentLastEl) agentLastEl.textContent = "Waiting..."; + if (agentObservationEl) agentObservationEl.textContent = "Waiting for first observation..."; + + const editShot = document.getElementById("edit-agent-shot-img"); + const editReqMeta = document.getElementById("edit-agent-req-meta"); + const editReqPrompt = document.getElementById("edit-agent-req-prompt"); + const editReqContext = document.getElementById("edit-agent-req-context"); + const editRespRaw = document.getElementById("edit-agent-resp-raw"); + const editLast = document.getElementById("edit-agent-last"); + if (editShot) editShot.removeAttribute("src"); + if (editReqMeta) editReqMeta.textContent = "No request yet"; + if (editReqPrompt) editReqPrompt.textContent = ""; + if (editReqContext) editReqContext.textContent = ""; + if (editRespRaw) editRespRaw.textContent = ""; + if (editLast) editLast.textContent = "Waiting..."; +} + +function showEditSpawnedAgentsTab() { + const btn = document.getElementById("vibe-tab-agents"); + btn?.click?.(); +} + +function getAgentById(id) { + const key = String(id || ""); + if (!key) return null; + return aiAgents.find((a) => a?.id === key) || null; +} + +function ensureAgentControlStrip() { + // Restrict spawned-agent controls to the right-panel "Spawned Agents" tab only. + const panelContent = document.getElementById("vibe-tab-agents-pane"); + if (!panelContent) return; + + let strip = document.getElementById("agent-control-strip"); + + // Re-parent strip if it ended up in the wrong panel after mode switch. + if (strip && strip.parentElement !== panelContent) { + strip.remove(); + strip = null; + agentUiSelectedLabelEl = null; + agentUiSpawnBtn = null; + agentUiFollowBtn = null; + agentUiStopBtn = null; + agentUiRemoveBtn = null; + agentUiTaskInputEl = null; + agentUiTaskRunBtn = null; + } + + if (agentUiSelectedLabelEl && agentUiFollowBtn && agentUiStopBtn && agentUiRemoveBtn) return; + + if (!strip) { + strip = document.createElement("div"); + strip.id = "agent-control-strip"; + strip.className = "agent-control-strip"; + strip.innerHTML = ` +
Selected: none
+
+ + + + +
+
+ + +
+ `; + panelContent.insertBefore(strip, panelContent.firstChild || null); + } + + agentUiSelectedLabelEl = document.getElementById("agent-selected-label"); + agentUiSpawnBtn = document.getElementById("agent-selected-spawn"); + agentUiFollowBtn = document.getElementById("agent-selected-follow"); + agentUiStopBtn = document.getElementById("agent-selected-stop"); + agentUiRemoveBtn = document.getElementById("agent-selected-remove"); + agentUiTaskInputEl = document.getElementById("agent-selected-task-input"); + agentUiTaskRunBtn = document.getElementById("agent-selected-task-run"); + + agentUiSpawnBtn?.addEventListener("click", () => { + void spawnOrMoveAiAtAim({ createNew: true, silent: false, ephemeral: false }).then(() => { + const newest = aiAgents[aiAgents.length - 1]; + if (newest?.id) selectAgentInspector(newest.id); + renderSelectedAgentControls(); + }); + showEditSpawnedAgentsTab(); + }); + agentUiFollowBtn?.addEventListener("click", () => { + const a = getAgentById(selectedAgentInspectorId); + if (!a) return; + if (agentCameraFollow && agentCameraFollowId === a.id) { + disableAgentCameraFollow(); + } else { + enableAgentCameraFollow(a.id); + } + renderSelectedAgentControls(); + }); + agentUiStopBtn?.addEventListener("click", () => { + const a = getAgentById(selectedAgentInspectorId); + if (!a) return; + stopAiAgent(a, "ui-stop"); + setStatus(`Stopped ${a.id}.`); + renderSelectedAgentControls(); + }); + agentUiRemoveBtn?.addEventListener("click", () => { + const a = getAgentById(selectedAgentInspectorId); + if (!a) return; + removeAiAgent(a, "ui-remove"); + setStatus(`Removed ${a.id}.`); + if (agentTask.active && aiAgents.length === 0) endAgentTask("all-agents-removed"); + renderSelectedAgentControls(); + }); + const runSelectedTask = () => { + const a = getAgentById(selectedAgentInspectorId); + if (!a) return; + const text = String(agentUiTaskInputEl?.value || "").trim(); + if (!text) return; + if (agentTask.active) endAgentTask("replace-task"); + void startAgentTask(text, { autoPool: false, targetAgentId: a.id }); + if (agentUiTaskInputEl) agentUiTaskInputEl.value = ""; + setStatus(`Running task on ${a.id}.`); + showEditSpawnedAgentsTab(); + }; + agentUiTaskRunBtn?.addEventListener("click", runSelectedTask); + agentUiTaskInputEl?.addEventListener("keydown", (e) => { + e.stopPropagation(); + if (e.key === "Enter") runSelectedTask(); + }); +} + +function renderSelectedAgentControls() { + ensureAgentControlStrip(); + if (!agentUiSelectedLabelEl || !agentUiFollowBtn || !agentUiStopBtn || !agentUiRemoveBtn) return; + const a = getAgentById(selectedAgentInspectorId); + const has = !!a; + agentUiSelectedLabelEl.textContent = has ? `Selected: ${a.id}` : "Selected: none"; + agentUiFollowBtn.disabled = !has; + agentUiStopBtn.disabled = !has; + agentUiRemoveBtn.disabled = !has; + agentUiFollowBtn.textContent = has && agentCameraFollow && agentCameraFollowId === a.id ? "Unfollow POV" : "Follow POV"; +} + +function getOrCreateAgentInspectorState(agentId) { + const id = String(agentId || ""); + if (!id) return { shot: "", request: null, response: null }; + if (!agentInspectorStateById.has(id)) { + agentInspectorStateById.set(id, { shot: "", request: null, response: null }); + } + return agentInspectorStateById.get(id); +} + +function renderAgentInspector(agentId = selectedAgentInspectorId) { + const id = String(agentId || ""); + if (!id) return; + const s = getOrCreateAgentInspectorState(id); + if (agentReqMetaEl) { + const base = s.request || { endpoint: "-", model: "-", prompt: "", context: {}, imageBytes: null, messages: [] }; + agentUiSetRequest(base); + agentReqMetaEl.textContent = `${agentReqMetaEl.textContent}\nagent: ${id}`; + const editMeta = document.getElementById("edit-agent-req-meta"); + if (editMeta) editMeta.textContent = `${editMeta.textContent}\nagent: ${id}`; + } + if (s.shot) agentUiSetShot(s.shot); + if (s.response) agentUiSetResponse(s.response); +} + +function selectAgentInspector(agentId) { + const id = String(agentId || ""); + if (!id) return; + selectedAgentInspectorId = id; + showEditSpawnedAgentsTab(); + // Force strip into correct panel on selection. + ensureAgentControlStrip(); + renderAgentInspector(id); + renderSelectedAgentControls(); + // Visual flash feedback. + const strip = document.getElementById("agent-control-strip"); + if (strip) { + strip.style.outline = "2px solid var(--accent-primary)"; + setTimeout(() => { strip.style.outline = ""; }, 600); + } +} + +function renderAgentTaskUi() { + ensureAgentControlStrip(); + const bar = document.getElementById("agent-command-bar"); + const hasAgent = aiAgents.length > 0; + + if (bar) bar.style.display = ""; + if (spawnAiBtn) spawnAiBtn.style.display = hasAgent ? "none" : ""; + + if (!agentTaskStatusEl || !agentTaskInputEl || !agentTaskStartBtn || !agentTaskEndBtn) return; + + if (!agentTask.active) { + agentTaskStatusEl.textContent = ""; + agentTaskInputEl.disabled = false; + agentTaskStartBtn.disabled = !hasAgent; + agentTaskEndBtn.disabled = true; + if (bar) bar.classList.remove("active"); + } else { + agentTaskStatusEl.textContent = "Running"; + agentTaskInputEl.disabled = true; + agentTaskStartBtn.disabled = true; + agentTaskEndBtn.disabled = false; + if (bar) bar.classList.add("active"); + } + updateSimCameraModeToggleUi(); + renderSelectedAgentControls(); +} + +function updateSimCameraModeToggleUi() { + if (!simCameraModeToggleBtn) return; + const isUserCam = simUserCameraMode === "user"; + simCameraModeToggleBtn.textContent = isUserCam ? "Camera: User" : "Camera: Agent"; + simCameraModeToggleBtn.classList.toggle("active", isUserCam); + simCameraModeToggleBtn.classList.toggle("tb-muted", !isUserCam); + simCameraModeToggleBtn.title = isUserCam + ? "Keep your user camera while the agent runs" + : "Follow the active agent while the task runs"; +} + +function enableAgentCameraFollow(agentId = selectedAgentInspectorId) { + if (aiAgents.length === 0) return; + const target = getAgentById(agentId) || aiAgents[0]; + if (!target) return; + agentCameraFollow = true; + agentCameraFollowId = target.id; + _agentFollowInitialized = false; + + // Unlock player controls so camera isn't fighting with pointer lock + controls?.unlock?.(); + + // Hide the player avatar + avatar.visible = false; + + // Hide crosshair and interaction hints during follow mode + const crosshair = document.getElementById("crosshair"); + if (crosshair) crosshair.style.display = "none"; + const hint = document.getElementById("interaction-hint"); + if (hint) hint.style.display = "none"; + + console.log("[AGENT CAM] Following agent"); + renderSelectedAgentControls(); +} + +function disableAgentCameraFollow() { + agentCameraFollow = false; + agentCameraFollowId = null; + + // Show all agent meshes again + for (const a of aiAgents) { + if (a?.group) a.group.visible = true; + } + + // Avatar mesh stays hidden (physics capsule still active) + + // Restore crosshair and interaction hints + const crosshair = document.getElementById("crosshair"); + if (crosshair) crosshair.style.display = ""; + const hint = document.getElementById("interaction-hint"); + if (hint) hint.style.display = ""; + + console.log("[AGENT CAM] Returning to player"); + renderSelectedAgentControls(); +} + +function updateAgentCameraFollow(dt) { + if (!agentCameraFollow || aiAgents.length === 0) return; + + const agent = getAgentById(agentCameraFollowId) || aiAgents[0]; + if (!agent) return; + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + + // Place camera at the real Go2 front-camera mount: GO2_CAMERA_HEIGHT above + // the ground and GO2_CAMERA_FORWARD along the agent's heading so the origin + // sits outside the body mesh (Go2's head-mounted RGB-D, not body-center). + const feetY = ay - ((agent.halfHeight || 0.25) + (agent.radius || 0.12)); + const eyeY = feetY + GO2_CAMERA_HEIGHT; + const eyeX = ax + Math.sin(yaw) * GO2_CAMERA_FORWARD; + const eyeZ = az + Math.cos(yaw) * GO2_CAMERA_FORWARD; + camera.position.set(eyeX, eyeY, eyeZ); + + // Compute forward direction exactly like visionCapture.js does + const cp = Math.cos(pitch); + const sp = Math.sin(pitch); + const fx = Math.sin(yaw) * cp; + const fy = sp; + const fz = Math.cos(yaw) * cp; + + // Use lookAt to match the VLM capture camera + camera.lookAt(eyeX + fx, eyeY + fy, eyeZ + fz); + + // Hide the agent's own mesh so it doesn't block the view + if (agent.group) agent.group.visible = false; +} + +async function startAgentTask(instruction, { autoPool = true, targetAgentId = null } = {}) { + const text = String(instruction || "").trim(); + if (!text) return; + + const now = Date.now(); + const taskState = { + active: true, + instruction: text, + startedAt: now, + finishedAt: 0, + finishedReason: "", + lastSummary: "", + }; + + // Determine which agents get this task + const target = targetAgentId ? getAgentById(targetAgentId) : null; + agentTaskTargetId = target?.id || null; + const targets = target ? [target] : aiAgents; + + for (const a of targets) { + _setAgentTask(a.id, { ...taskState }); + a._taskStartedAt = now; + if (a?.vlm) a.vlm.enabled = true; + } + + agentUiPush(`${new Date().toLocaleTimeString()}\nTASK START\n${text}${target ? ` [${target.id}]` : ` [${targets.length} agents]`}`); + renderAgentTaskUi(); + + if (simUserCameraMode === "agent") enableAgentCameraFollow(); +} + +function endAgentTask(reason = "manual", agentId = null) { + if (agentId) { + // End task for a specific agent + const task = _agentTasks.get(agentId); + if (task?.active) { + task.active = false; + task.finishedAt = Date.now(); + task.finishedReason = reason; + _agentTasks.set(agentId, task); + } + agentUiPush(`${new Date().toLocaleTimeString()}\nTASK END (${reason}) [${agentId}]`); + } else { + // End all tasks + for (const [id, task] of _agentTasks) { + if (task.active) { + task.active = false; + task.finishedAt = Date.now(); + task.finishedReason = reason; + } + } + agentTask.active = false; + agentTask.finishedAt = Date.now(); + agentTask.finishedReason = reason; + agentUiPush(`${new Date().toLocaleTimeString()}\nTASK END ALL (${reason})`); + } + agentTaskTargetId = null; + + // Check if any agent still has an active task + const anyActive = [..._agentTasks.values()].some((t) => t.active); + if (!anyActive) { + agentTask.active = false; + disableAgentCameraFollow(); + } + + renderAgentTaskUi(); + +} + +function rebuildTagMarkers() { + while (tagsGroup.children.length) tagsGroup.remove(tagsGroup.children[0]); + + for (const t of tags) { + if (!t.position) continue; + const m = new THREE.Mesh(markerGeom, t.id === selectedTagId ? markerMatActive : markerMat); + m.position.set(t.position.x, t.position.y, t.position.z); + m.userData.tagId = t.id; + m.renderOrder = 1000; + tagsGroup.add(m); + + const r = Number(t.radius ?? 1.5); + const shell = new THREE.Mesh(radiusGeom, radiusMat); + shell.position.copy(m.position); + shell.scale.setScalar(Math.max(0.01, r)); + shell.userData.tagId = t.id; + shell.userData.isRadius = true; + tagsGroup.add(shell); + } + + updateMarkerMaterials(); +} + +function updateMarkerMaterials() { + for (const child of tagsGroup.children) { + if (!child.isMesh) continue; + if (child.userData?.isRadius) continue; + child.material = child.userData.tagId === selectedTagId ? markerMatActive : markerMat; + } +} + +function renderAssetModal() { + // No-op in sim-only mode (asset upload modal not present) +} + +function base64FromArrayBuffer(buf) { + const bytes = new Uint8Array(buf); + let binary = ""; + const chunk = 0x8000; + for (let i = 0; i < bytes.length; i += chunk) { + binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); + } + return btoa(binary); +} + +function arrayBufferFromBase64(base64) { + const bin = atob(base64); + const len = bin.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i); + return bytes.buffer; +} + +function normalizeAsset(a) { + if (!a || typeof a !== "object") return null; + // Ensure pickable property exists + if (typeof a.pickable !== "boolean") a.pickable = false; + if (typeof a.bumpable !== "boolean") a.bumpable = false; + if (!Number.isFinite(a.bumpResponse)) a.bumpResponse = 0.9; + if (!Number.isFinite(a.bumpDamping)) a.bumpDamping = 0.9; + // New schema: states is array + if (Array.isArray(a.states)) { + if (!a.currentStateId && a.states[0]?.id) a.currentStateId = a.states[0].id; + // Ensure interactions exist per state. + for (const s of a.states) { + if (!Array.isArray(s.interactions)) s.interactions = []; + } + // Backfill actions from interactions if missing. + if (!Array.isArray(a.actions) || a.actions.length === 0) { + a.actions = []; + for (const s of a.states) { + for (const it of s.interactions) { + a.actions.push({ id: it.id || `act_${s.id}_${it.to}`, label: it.label || "toggle", from: s.id, to: it.to }); + } + } + } else { + // Backfill interactions from actions if missing. + const byFrom = new Map(); + for (const act of a.actions) { + if (!byFrom.has(act.from)) byFrom.set(act.from, []); + byFrom.get(act.from).push({ id: act.id, label: act.label, to: act.to }); + } + for (const s of a.states) { + if (!s.interactions || s.interactions.length === 0) s.interactions = byFrom.get(s.id) || []; + } + } + return a; + } + // Old schema: states is {A,B} + if (a.states && typeof a.states === "object") { + const out = { + id: a.id, + title: a.title || "", + notes: a.notes || "", + states: [], + currentStateId: a.currentState || "A", + actions: Array.isArray(a.actions) ? a.actions : [], + transform: a.transform || null, + bumpable: a.bumpable === true, + bumpResponse: Number.isFinite(a.bumpResponse) ? a.bumpResponse : 0.9, + bumpDamping: Number.isFinite(a.bumpDamping) ? a.bumpDamping : 0.9, + }; + const A = a.states.A; + const B = a.states.B; + if (A) out.states.push({ id: "A", name: A.name || "stateA", glbName: A.glbName || "", dataBase64: A.dataBase64 || "", interactions: [] }); + if (B) out.states.push({ id: "B", name: B.name || "stateB", glbName: B.glbName || "", dataBase64: B.dataBase64 || "", interactions: [] }); + if (!out.currentStateId) out.currentStateId = out.states[0]?.id || "A"; + out.actions = Array.isArray(out.actions) ? out.actions : []; + // Backfill interactions from actions. + const byFrom = new Map(); + for (const act of out.actions) { + if (!byFrom.has(act.from)) byFrom.set(act.from, []); + byFrom.get(act.from).push({ id: act.id, label: act.label, to: act.to }); + } + for (const s of out.states) s.interactions = byFrom.get(s.id) || []; + return out; + } + return a; +} + +function getSelectedAsset() { + return assets.find((a) => a.id === selectedAssetId) || null; +} + +function renderAssetsList() { + if (!assetsListEl) return; + assetsListEl.innerHTML = ""; + for (const a of assets) { + const el = document.createElement("div"); + el.className = "tag-item" + (a.id === selectedAssetId ? " active" : ""); + const sId = a.currentStateId || a.currentState || "A"; + const stateObj = Array.isArray(a.states) ? a.states.find((s) => s.id === sId) : a.states?.[sId]; + const label = a.title || stateObj?.glbName || "(asset)"; + const kind = stateObj?.scene || stateObj?.shapeScene ? "shape" : "glb"; + el.innerHTML = `${escapeHtml(label)}${kind}`; + el.addEventListener("click", () => selectAsset(a.id)); + assetsListEl.appendChild(el); + } +} + +function selectAsset(id) { + selectedAssetId = id; + if (id) { + selectedPrimitiveId = null; + } + renderAssetsList(); +} + +function persistSelectedAssetTransform() { + const a = getSelectedAsset(); + if (!a) return; + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (!obj) return; + a.transform = { + position: { x: obj.position.x, y: obj.position.y, z: obj.position.z }, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }, + }; + saveTagsForWorld(); + if (!a.bumpable) rebuildAssetCollider(a.id); +} + +function normalizeShapeStateScene(sceneLike) { + const raw = sceneLike || { tags: [], primitives: [], lights: [], groups: [] }; + return { + tags: Array.isArray(raw.tags) ? raw.tags : [], + primitives: Array.isArray(raw.primitives) ? raw.primitives : [], + lights: Array.isArray(raw.lights) ? raw.lights : [], + groups: Array.isArray(raw.groups) ? raw.groups : [], + }; +} + +function buildShapeStateRoot(state, assetId, fixedPivotCenter = null) { + const sceneState = normalizeShapeStateScene(state?.scene || state?.shapeScene); + const root = new THREE.Group(); + const primMap = new Map(); + for (const p of sceneState.primitives) { + const geom = createPrimitiveGeometry(p.type, p.dimensions || {}); + const mat = createPrimitiveMaterial(p.material || {}); + const mesh = new THREE.Mesh(geom, mat); + applyPrimitiveCutoutShader(mesh, p); + mesh.name = `assetPrim:${assetId}:${p.id || randId()}`; + mesh.userData.assetId = assetId; + mesh.userData.isAssetPrimitive = true; + mesh.castShadow = p.castShadow !== false; + mesh.receiveShadow = p.receiveShadow !== false; + const tr = p.transform || {}; + if (tr.position) mesh.position.set(tr.position.x || 0, tr.position.y || 0, tr.position.z || 0); + if (tr.rotation) mesh.rotation.set(tr.rotation.x || 0, tr.rotation.y || 0, tr.rotation.z || 0); + if (tr.scale) mesh.scale.set(tr.scale.x ?? 1, tr.scale.y ?? 1, tr.scale.z ?? 1); + root.add(mesh); + if (p.id) primMap.set(p.id, mesh); + } + for (const g of sceneState.groups || []) { + if (!Array.isArray(g.children) || g.children.length === 0) continue; + const subgroup = new THREE.Group(); + subgroup.name = `assetGroup:${assetId}:${g.id || randId()}`; + root.add(subgroup); + for (const cid of g.children) { + const child = primMap.get(cid); + if (!child) continue; + subgroup.add(child); + } + } + + // Re-center: move the pivot to the bounding-box center so the transform + // gizmo appears on the asset rather than at an arbitrary offset. + root.updateMatrixWorld(true); + const bbox = new THREE.Box3().setFromObject(root); + if (!bbox.isEmpty()) { + const autoCenter = bbox.getCenter(new THREE.Vector3()); + const center = fixedPivotCenter ? fixedPivotCenter.clone() : autoCenter; + for (const child of root.children) { + child.position.sub(center); + } + root.position.copy(center); + root.userData._pivotCenter = center.clone(); + } + + return root; +} + +function disposeShapeStateRoot(root) { + if (!root) return; + root.traverse((obj) => { + if (!obj?.isMesh) return; + obj.geometry?.dispose?.(); + disposePrimitiveMaterial(obj.material); + }); +} + +async function instantiateAsset(a) { + if (!a?.states) return; + const sId = a.currentStateId || a.currentState || (Array.isArray(a.states) ? a.states[0]?.id : "A"); + const state = Array.isArray(a.states) + ? a.states.find((s) => s.id === sId) || a.states[0] + : a.states[sId] || a.states.A; + let root = null; + if (state?.scene || state?.shapeScene) { + let fixedPivotCenter = null; + if (a._shapePivotCenter + && Number.isFinite(a._shapePivotCenter.x) + && Number.isFinite(a._shapePivotCenter.y) + && Number.isFinite(a._shapePivotCenter.z)) { + fixedPivotCenter = new THREE.Vector3(a._shapePivotCenter.x, a._shapePivotCenter.y, a._shapePivotCenter.z); + } else if (Array.isArray(a.states) && a.states.length > 0) { + const anchorState = a.states[0]; + const anchorRoot = buildShapeStateRoot(anchorState, `${a.id}:anchor`); + const anchorCenter = anchorRoot.userData?._pivotCenter; + if (anchorCenter) { + fixedPivotCenter = anchorCenter.clone(); + a._shapePivotCenter = { x: anchorCenter.x, y: anchorCenter.y, z: anchorCenter.z }; + } + disposeShapeStateRoot(anchorRoot); + } + root = buildShapeStateRoot(state, a.id, fixedPivotCenter); + const rootCenter = root.userData?._pivotCenter; + if (rootCenter && !a._shapePivotCenter) { + a._shapePivotCenter = { x: rootCenter.x, y: rootCenter.y, z: rootCenter.z }; + } + } else if (state?.dataBase64) { + const buf = arrayBufferFromBase64(state.dataBase64); + const url = URL.createObjectURL(new Blob([buf], { type: "model/gltf-binary" })); + const gltf = await new Promise((resolve, reject) => { + gltfLoader.load(url, (g) => resolve(g), undefined, (e) => reject(e)); + }); + URL.revokeObjectURL(url); + root = gltf.scene; + } else { + return; + } + root.name = `asset:${a.id}`; + const wantShadow = a.castShadow === true; // opt-in, default OFF + const wantReceive = a.receiveShadow === true; // opt-in, default OFF + + root.traverse((m) => { + if (m.isMesh) { + if (!m.userData?.isAssetPrimitive) m.castShadow = false; // GLB assets keep cheap shadow behavior + m.receiveShadow = wantReceive; + m.userData.assetId = a.id; + } + }); + + // Pre-compute local bounding sphere ONCE (cached — never call setFromObject again) + const bbox = new THREE.Box3().setFromObject(root); + const localSphere = new THREE.Sphere(); + bbox.getBoundingSphere(localSphere); + const localCenter = localSphere.center.clone(); + root.worldToLocal(localCenter); + root.userData._localSphereCenter = localCenter; + root.userData._localSphereRadius = Math.max(localSphere.radius, 0.2); + + // Blob shadow: a cheap flat gradient circle beneath the asset. + // Uses zero shadow-map resources — just a textured plane with transparency. + if (wantShadow) { + const bboxSize = bbox.getSize(new THREE.Vector3()); + const localGroundY = bbox.min.y + 0.005; + const blob = createBlobShadow(a.id, bboxSize.x, bboxSize.z, localGroundY, { + opacity: a.blobShadow?.opacity ?? 0.5, + scale: a.blobShadow?.scale ?? 1.0, + stretch: a.blobShadow?.stretch ?? 1.0, + rotationDeg: a.blobShadow?.rotationDeg ?? 0, + offsetX: a.blobShadow?.offsetX ?? 0, + offsetY: a.blobShadow?.offsetY ?? 0, + offsetZ: a.blobShadow?.offsetZ ?? 0, + }); + if (blob) root.add(blob); + } + + const tr = a.transform || {}; + if (tr.position) root.position.set(tr.position.x, tr.position.y, tr.position.z); + if (tr.rotation) root.rotation.set(tr.rotation.x, tr.rotation.y, tr.rotation.z); + if (tr.scale) root.scale.set(tr.scale.x, tr.scale.y, tr.scale.z); + assetsGroup.add(root); + await rebuildAssetCollider(a.id); +} + +async function setAssetState(assetId, nextState) { + const a = assets.find((x) => x.id === assetId); + if (!a) return; + const exists = Array.isArray(a.states) ? a.states.some((s) => s.id === nextState) : !!a.states?.[nextState]; + if (!exists) return; + a.currentStateId = nextState; + saveTagsForWorld(); + // Replace visual + const existing = assetsGroup.getObjectByName(`asset:${a.id}`); + if (existing?.parent) existing.parent.remove(existing); + await instantiateAsset(a); + renderAssetsList(); + selectAsset(a.id); +} + +async function applyAssetAction(assetId, actionId) { + const a = assets.find((x) => x.id === assetId); + if (!a) return false; + + const act = (a.actions || []).find((x) => x.id === actionId) || null; + if (!act) return false; + const cur = a.currentStateId || a.currentState || "A"; + if (cur !== act.from) return false; + await setAssetState(assetId, act.to); + return true; +} + +function getNearbyAssetsForAgent(agent, maxDist = 1.0) { + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch); + const sp = Math.sin(pitch); + + // Full 3D forward direction (with pitch) - used for raycasting + const forward3D = _tmpV1.set(Math.sin(yaw) * cp, sp, Math.cos(yaw) * cp).normalize(); + + // Horizontal-only forward direction (yaw only, no pitch) - used for "in front" check + // BUG FIX: Previously used forward3D which includes pitch, causing dot product to be + // artificially reduced when looking up/down (cos(pitch) scaling factor) + const forwardHoriz = new THREE.Vector3(Math.sin(yaw), 0, Math.cos(yaw)).normalize(); + + const eye = _tmpV2.set(ax, ay + PLAYER_EYE_HEIGHT * 0.9, az); + + const results = []; + for (const a of assets) { + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (!obj) continue; + + // Use cached sphere center (O(1) — no vertex traversal) + const _agentSphere = new THREE.Sphere(); + if (!getAssetWorldSphere(obj, _agentSphere)) continue; + const center = _agentSphere.center; + + const dx = center.x - ax; + const dy = center.y - ay; + const dz = center.z - az; + const dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (dist > maxDist) continue; + + // Horizontal direction to object (for "in front" check) + const toHoriz = _tmpV3.set(dx, 0, dz); + const horizLen = toHoriz.length() || 1; + toHoriz.multiplyScalar(1 / horizLen); + + // BUG FIX: Use horizontal forward for horizontal "in front" check + // Threshold relaxed from 0.92 (~23°) to 0.7 (~45°) for better usability + const inFrontHoriz = forwardHoriz.dot(toHoriz) > 0.7; + + let isLookedAt = false; + + // Debug: log for specific asset checks + const debugThis = a.title?.toLowerCase().includes('bathtub') || a.title?.toLowerCase().includes('tub'); + + if (debugThis) { + console.log(`[RAYCAST DEBUG] Checking "${a.title}" (${a.id})`); + console.log(` Eye position:`, eye.toArray().map(v => v.toFixed(2))); + console.log(` Object center:`, [center.x.toFixed(2), center.y.toFixed(2), center.z.toFixed(2)]); + console.log(` Forward3D:`, forward3D.toArray().map(v => v.toFixed(2))); + console.log(` ForwardHoriz:`, [forwardHoriz.x.toFixed(2), forwardHoriz.y.toFixed(2), forwardHoriz.z.toFixed(2)]); + console.log(` ToHoriz:`, [toHoriz.x.toFixed(2), toHoriz.y.toFixed(2), toHoriz.z.toFixed(2)]); + const dotVal = forwardHoriz.dot(toHoriz); + console.log(` Horiz dot product:`, dotVal.toFixed(3), `(need > 0.7 for inFront, > 0.3 for proximity)`); + console.log(` inFrontHoriz (>0.7):`, inFrontHoriz); + console.log(` roughlyInFront (>0.3):`, dotVal > 0.3); + } + + // For interaction purposes, we use a very lenient "roughly in front" check + // The bounding box center can be off to the side for wide objects + const roughlyInFront = forwardHoriz.dot(toHoriz) > 0.3; // ~72° cone + + if (inFrontHoriz || roughlyInFront) { + // Cheap bounding-sphere ray test instead of expensive recursive mesh raycast + const objNode = assetsGroup.getObjectByName(`asset:${a.id}`); + if (objNode) { + const _tmpSphere = new THREE.Sphere(); + if (!getAssetWorldSphere(objNode, _tmpSphere)) { /* skip */ } + _tmpSphere.radius = Math.max(_tmpSphere.radius, 0.3); + + // Test look direction against bounding sphere + const lookRay = new THREE.Ray(eye, forward3D); + if (lookRay.intersectsSphere(_tmpSphere)) { + isLookedAt = true; + } + + // Also test toward center direction (catches pitch misalignment) + if (!isLookedAt) { + const toCenter = new THREE.Vector3( + center.x - eye.x, center.y - eye.y, center.z - eye.z + ).normalize(); + const lookAlignment = forward3D.dot(toCenter); + if (lookAlignment > 0.5) { + const centerRay = new THREE.Ray(eye, toCenter); + if (centerRay.intersectsSphere(_tmpSphere)) { + isLookedAt = true; + } + } + } + } + } + + // Method 3: If close enough and roughly in front, allow interaction even if raycast fails + // This handles cases where: + // - Mesh geometry doesn't raycast well + // - Wide objects have their center off to the side + if (!isLookedAt && dist < 1.5 && roughlyInFront) { + if (debugThis) { + console.log(` Method 3 (proximity fallback): dist=${dist.toFixed(2)}, roughlyInFront=${roughlyInFront}`); + } + isLookedAt = true; + } + + if (debugThis) { + console.log(` Final isLookedAt:`, isLookedAt); + } + + const stateKey = a.currentStateId || a.currentState || "A"; + const stateObj = Array.isArray(a.states) + ? a.states.find((s) => s.id === stateKey) + : a.states?.[stateKey]; + const stateName = stateObj?.name || stateKey; + const holdStatus = isAssetHeld(a.id); + results.push({ + id: a.id, + title: a.title || "", + notes: a.notes || "", + dist, + isLookedAt, + currentState: stateKey, + currentStateName: stateName, + actions: (a.actions || []).filter((x) => x.from === stateKey).map((x) => ({ id: x.id, label: x.label, from: x.from, to: x.to })), + pickable: a.pickable || false, + isHeld: holdStatus.held, + heldBy: holdStatus.by || null, + }); + } + + results.sort((a, b) => a.dist - b.dist); + return results.slice(0, 20); +} + +function getNearbyPrimitivesForAgent(agent, maxDist = 2.5) { + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch); + const sp = Math.sin(pitch); + const forward3D = _tmpV1.set(Math.sin(yaw) * cp, sp, Math.cos(yaw) * cp).normalize(); + const forwardHoriz = _tmpV2.set(Math.sin(yaw), 0, Math.cos(yaw)).normalize(); + const eye = _tmpV3.set(ax, ay + PLAYER_EYE_HEIGHT * 0.9, az); + + const out = []; + for (const p of primitives) { + const obj = primitivesGroup.getObjectByName(`prim:${p.id}`); + if (!obj) continue; + const center = obj.getWorldPosition(new THREE.Vector3()); + const dx = center.x - ax; + const dy = center.y - ay; + const dz = center.z - az; + const dist = Math.hypot(dx, dy, dz); + if (dist > maxDist) continue; + + const toObj = new THREE.Vector3(center.x - eye.x, center.y - eye.y, center.z - eye.z).normalize(); + const toHoriz = new THREE.Vector3(dx, 0, dz); + const horizLen = toHoriz.length() || 1; + toHoriz.multiplyScalar(1 / horizLen); + const lookAlignment = forward3D.dot(toObj); + const horizAlignment = forwardHoriz.dot(toHoriz); + const isLookedAt = lookAlignment > 0.82 || (dist < 1.6 && horizAlignment > 0.35); + + out.push({ + id: p.id, + name: p.name || p.type || "primitive", + type: p.type || "primitive", + dist, + isLookedAt, + }); + } + out.sort((a, b) => a.dist - b.dist); + return out.slice(0, 20); +} + + +async function agentInteractAsset({ agent, assetId, actionId }) { + console.log(`[INTERACT] Attempting interaction: assetId="${assetId}", actionId="${actionId}"`); + + const candidates = getNearbyAssetsForAgent(agent, 1.5); // Interaction distance + + // Debug: if no candidates, show what assets exist + if (candidates.length === 0) { + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + console.log(`[INTERACT] Agent position:`, [ax.toFixed(2), ay.toFixed(2), az.toFixed(2)]); + console.log(`[INTERACT] All assets in scene:`, assets.map(a => { + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (!obj) return { id: a.id, title: a.title, inScene: false }; + const _ds = new THREE.Sphere(); + getAssetWorldSphere(obj, _ds); + const center = _ds.center; + const dx = center.x - ax; + const dy = center.y - ay; + const dz = center.z - az; + const dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + return { id: a.id, title: a.title, dist: dist.toFixed(2), center: [center.x.toFixed(2), center.y.toFixed(2), center.z.toFixed(2)] }; + })); + } + + console.log(`[INTERACT] Nearby candidates:`, candidates.map(c => ({ + id: c.id, + title: c.title, + dist: c.dist.toFixed(2), + isLookedAt: c.isLookedAt, + currentState: c.currentState, + actions: c.actions.map(a => `${a.id}:${a.label}`) + }))); + + const target = candidates.find((x) => x.id === assetId); + if (!target) { + console.warn(`[INTERACT] FAIL: Asset "${assetId}" not in nearby candidates`); + return { ok: false, reason: "not-nearby" }; + } + + console.log(`[INTERACT] Found target: dist=${target.dist.toFixed(2)}m, isLookedAt=${target.isLookedAt}, currentState=${target.currentState}`); + + if (!target.isLookedAt && target.dist > 1.2) { + console.warn(`[INTERACT] FAIL: Asset "${assetId}" not looked at (isLookedAt=false)`); + return { ok: false, reason: "not-looking" }; + } + if (!target.isLookedAt && target.dist <= 1.2) { + console.log(`[INTERACT] Allowing close-range interaction despite look mismatch (dist=${target.dist.toFixed(2)}m).`); + } + + // Check if the actionId exists in the target's available actions + const availableAction = target.actions.find(a => a.id === actionId); + if (!availableAction) { + console.warn(`[INTERACT] actionId "${actionId}" not in available actions:`, target.actions); + // Try to find by label instead + const byLabel = target.actions.find(a => a.label?.toLowerCase() === actionId?.toLowerCase()); + if (byLabel) { + console.log(`[INTERACT] Found action by label match: "${byLabel.id}"`); + actionId = byLabel.id; + } + } + + const ok = await applyAssetAction(assetId, actionId); + console.log(`[INTERACT] applyAssetAction result: ${ok}`); + + if (!ok) { + // Diagnose why it failed + const asset = assets.find(a => a.id === assetId); + if (asset) { + const curState = asset.currentStateId || asset.currentState || "A"; + const allActions = asset.actions || []; + const matchingAction = allActions.find(a => a.id === actionId); + console.warn(`[INTERACT] applyAssetAction FAILED diagnosis:`, { + currentState: curState, + requestedActionId: actionId, + actionFound: !!matchingAction, + actionFromState: matchingAction?.from, + actionToState: matchingAction?.to, + fromMatchesCurrent: matchingAction?.from === curState, + allActionIds: allActions.map(a => `${a.id}(${a.from}->${a.to})`) + }); + } + } + + return { ok, reason: ok ? "ok" : "invalid-action" }; +} + +// ============================================================================ +// PLAYER INTERACTION SYSTEM +// ============================================================================ +const PLAYER_INTERACT_DISTANCE = 1.5; // Max distance player can interact with assets +const _playerInteractRaycaster = new THREE.Raycaster(); +let _interactionPopup = null; +let _currentInteractableAsset = null; +let _crosshairInteractCycleIndex = 0; +let _crosshairInteractCycleSig = ""; +let _crosshairInteractCandidates = []; + +// ============================================================================ +// PICK UP / DROP SYSTEM +// ============================================================================ +let playerHeldAsset = null; // Asset ID currently held by player +let playerHeldGroupId = null; // Group ID currently held by player +const agentHeldAssets = new Map(); // Map - assets held by each agent + +/** + * Check if an asset is currently being held by anyone + */ +function isAssetHeld(assetId) { + if (playerHeldAsset === assetId) return { held: true, by: "player" }; + for (const [agentId, heldId] of agentHeldAssets.entries()) { + if (heldId === assetId) return { held: true, by: "agent", agentId }; + } + return { held: false }; +} + +function isGroupHeld(groupId) { + if (playerHeldGroupId === groupId) return { held: true, by: "player" }; + return { held: false }; +} + +function getGroupById(groupId) { + return groups.find((g) => g.id === groupId) || null; +} + +function getGroupCentroid(groupId) { + const g = getGroupById(groupId); + if (!g || !Array.isArray(g.children) || g.children.length === 0) return null; + let cx = 0, cy = 0, cz = 0, count = 0; + for (const cid of g.children) { + const p = primitives.find((x) => x.id === cid); + const pos = p?.transform?.position; + if (!pos) continue; + cx += pos.x || 0; + cy += pos.y || 0; + cz += pos.z || 0; + count++; + } + if (count === 0) return null; + return { x: cx / count, y: cy / count, z: cz / count }; +} + +function playerPickUpGroup(groupId) { + const g = getGroupById(groupId); + if (!g) return { ok: false, reason: "not-found" }; + if (!g.pickable) return { ok: false, reason: "not-pickable" }; + if (playerHeldAsset || playerHeldGroupId) return { ok: false, reason: "hands-full" }; + const holdStatus = isGroupHeld(groupId); + if (holdStatus.held) return { ok: false, reason: "already-held", by: holdStatus.by }; + + playerHeldGroupId = groupId; + for (const cid of g.children || []) { + const mesh = primitivesGroup.getObjectByName(`prim:${cid}`); + if (mesh) mesh.visible = false; + const prim = primitives.find((p) => p.id === cid); + if (prim) removePrimitiveCollider(prim); + } + setStatus(`Picked up group: ${g.name || "group"}`); + return { ok: true }; +} + +function playerDropGroup() { + if (!playerHeldGroupId) return { ok: false, reason: "not-holding" }; + const g = getGroupById(playerHeldGroupId); + if (!g) { + playerHeldGroupId = null; + return { ok: false, reason: "not-found" }; + } + const centroid = getGroupCentroid(g.id); + if (!centroid) { + playerHeldGroupId = null; + return { ok: false, reason: "invalid-group" }; + } + // Raycast from crosshair to find drop point + const dropRay = new THREE.Raycaster(); + dropRay.setFromCamera({ x: 0, y: 0 }, camera); + dropRay.far = 6; + const candidates = []; + // Exclude held group's own meshes + const heldChildSet = new Set(g.children || []); + primitivesGroup.traverse((c) => { + if (c.isMesh && !heldChildSet.has(c.name?.replace("prim:", ""))) candidates.push(c); + }); + assetsGroup.traverse((c) => { if (c.isMesh) candidates.push(c); }); + scene.traverse((c) => { + if (c.isMesh && !candidates.includes(c) && c.parent !== assetsGroup && c.parent !== primitivesGroup) candidates.push(c); + }); + const hits = dropRay.intersectObjects(candidates, false); + let dropPos; + if (hits.length > 0) { + dropPos = { x: hits[0].point.x, y: hits[0].point.y, z: hits[0].point.z }; + } else { + const forward = new THREE.Vector3(); + camera.getWorldDirection(forward); + dropPos = { + x: camera.position.x + forward.x * 1.5, + y: 0, + z: camera.position.z + forward.z * 1.5, + }; + } + const dx = dropPos.x - centroid.x; + const dz = dropPos.z - centroid.z; + for (const cid of g.children || []) { + const prim = primitives.find((p) => p.id === cid); + if (!prim?.transform?.position) continue; + prim.transform.position.x += dx; + prim.transform.position.z += dz; + const mesh = primitivesGroup.getObjectByName(`prim:${cid}`); + if (mesh) { + mesh.position.x = prim.transform.position.x; + mesh.position.y = prim.transform.position.y; + mesh.position.z = prim.transform.position.z; + mesh.visible = true; + } + rebuildPrimitiveColliderSync(prim); + } + const droppedId = playerHeldGroupId; + playerHeldGroupId = null; + saveTagsForWorld(); + setStatus(`Dropped group: ${g.name || "group"}`); + return { ok: true, groupId: droppedId }; +} + +/** + * Pick up an asset (for player) + */ +function playerPickUpAsset(assetId) { + const asset = assets.find(a => a.id === assetId); + if (!asset) return { ok: false, reason: "not-found" }; + if (!asset.pickable) return { ok: false, reason: "not-pickable" }; + + const holdStatus = isAssetHeld(assetId); + if (holdStatus.held) return { ok: false, reason: "already-held", by: holdStatus.by }; + + if (playerHeldAsset) return { ok: false, reason: "hands-full" }; + + playerHeldAsset = assetId; + + // Hide the asset from the scene (it's now "in hand") + const obj = assetsGroup.getObjectByName(`asset:${assetId}`); + if (obj) obj.visible = false; + + // Remove collider while held + removeAssetCollider(assetId); + + console.log(`[PICKUP] Player picked up: ${asset.title || assetId}`); + setStatus(`Picked up: ${asset.title || "item"}`); + return { ok: true }; +} + +/** + * Drop the held asset (for player) + */ +function playerDropAsset() { + if (!playerHeldAsset) return { ok: false, reason: "not-holding" }; + + const asset = assets.find(a => a.id === playerHeldAsset); + if (!asset) { + playerHeldAsset = null; + return { ok: false, reason: "not-found" }; + } + + // Raycast from crosshair to find where the player is looking + const dropRay = new THREE.Raycaster(); + dropRay.setFromCamera({ x: 0, y: 0 }, camera); + dropRay.far = 6; + // Collect all scene meshes except the held asset itself + const candidates = []; + primitivesGroup.traverse((c) => { if (c.isMesh) candidates.push(c); }); + assetsGroup.traverse((c) => { + if (c.isMesh && !c.name?.includes(playerHeldAsset)) candidates.push(c); + }); + // Also include splat / collision meshes if any + scene.traverse((c) => { + if (c.isMesh && !candidates.includes(c) && c.parent !== assetsGroup && c.parent !== primitivesGroup) candidates.push(c); + }); + const hits = dropRay.intersectObjects(candidates, false); + let dropPos; + if (hits.length > 0) { + // Place at the hit point + dropPos = hits[0].point.clone(); + } else { + // Fallback: fixed distance along look direction, at ground level + const forward = new THREE.Vector3(); + camera.getWorldDirection(forward); + const fallbackDist = 1.5; + dropPos = new THREE.Vector3( + camera.position.x + forward.x * fallbackDist, + 0, + camera.position.z + forward.z * fallbackDist + ); + } + + // Update asset transform + asset.transform.position = { x: dropPos.x, y: dropPos.y, z: dropPos.z }; + + // Show and reposition the asset — traverse to ensure all children are visible + const obj = assetsGroup.getObjectByName(`asset:${playerHeldAsset}`); + if (obj) { + obj.position.copy(dropPos); + obj.visible = true; + obj.traverse((child) => { child.visible = true; }); + } else { + // Object was lost — re-instantiate from asset data + console.warn(`[DROP] 3D object missing for ${playerHeldAsset}, re-instantiating...`); + instantiateAsset(asset); + } + + // Rebuild collider + rebuildAssetCollider(playerHeldAsset); + + console.log(`[DROP] Player dropped: ${asset.title || playerHeldAsset}`); + setStatus(`Dropped: ${asset.title || "item"}`); + + const droppedId = playerHeldAsset; + playerHeldAsset = null; + saveTagsForWorld(); + + return { ok: true, assetId: droppedId }; +} + +/** + * Pick up an asset (for AI agent) + */ +function agentPickUpAsset(agent, assetId) { + const agentId = agent.id || "default"; + const asset = assets.find(a => a.id === assetId); + + if (!asset) return { ok: false, reason: "not-found" }; + if (!asset.pickable) return { ok: false, reason: "not-pickable" }; + + const holdStatus = isAssetHeld(assetId); + if (holdStatus.held) return { ok: false, reason: "already-held", by: holdStatus.by }; + + if (agentHeldAssets.has(agentId)) return { ok: false, reason: "hands-full" }; + + // Check distance + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const obj = assetsGroup.getObjectByName(`asset:${assetId}`); + if (obj) { + const _pickSphere = new THREE.Sphere(); + getAssetWorldSphere(obj, _pickSphere); + const center = _pickSphere.center; + const dist = Math.sqrt( + Math.pow(center.x - ax, 2) + + Math.pow(center.y - ay, 2) + + Math.pow(center.z - az, 2) + ); + if (dist > 1.5) return { ok: false, reason: "too-far", dist }; + } + + agentHeldAssets.set(agentId, assetId); + + // Hide the asset from the scene + if (obj) obj.visible = false; + + // Remove collider while held + removeAssetCollider(assetId); + + console.log(`[PICKUP] Agent ${agentId} picked up: ${asset.title || assetId}`); + return { ok: true }; +} + +/** + * Drop the held asset (for AI agent) + */ +function agentDropAsset(agent) { + const agentId = agent.id || "default"; + const assetId = agentHeldAssets.get(agentId); + + if (!assetId) return { ok: false, reason: "not-holding" }; + + const asset = assets.find(a => a.id === assetId); + if (!asset) { + agentHeldAssets.delete(agentId); + return { ok: false, reason: "not-found" }; + } + + // Calculate drop position (in front of agent) + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const dropDist = 0.6; + + const dropPos = new THREE.Vector3( + ax + Math.sin(yaw) * dropDist, + ay + 0.1, // Slightly above ground + az + Math.cos(yaw) * dropDist + ); + + // Update asset transform + asset.transform.position = { x: dropPos.x, y: dropPos.y, z: dropPos.z }; + + // Show and reposition the asset + const obj = assetsGroup.getObjectByName(`asset:${assetId}`); + if (obj) { + obj.position.copy(dropPos); + obj.visible = true; + } + + // Rebuild collider + rebuildAssetCollider(assetId); + + console.log(`[DROP] Agent ${agentId} dropped: ${asset.title || assetId}`); + + agentHeldAssets.delete(agentId); + saveTagsForWorld(); + + return { ok: true, assetId }; +} + +/** + * Remove collider for an asset (when picked up) + */ +function removeAssetCollider(assetId) { + const handle = _assetColliderHandles.get(assetId); + if (handle != null && rapierWorld) { + const collider = rapierWorld.getCollider(handle); + if (collider) rapierWorld.removeCollider(collider, true); + _assetColliderHandles.delete(assetId); + } +} + +/** + * Get what the player is currently holding + */ +function getPlayerHeldAsset() { + if (!playerHeldAsset) return null; + return assets.find(a => a.id === playerHeldAsset) || null; +} + +/** + * Get what an agent is currently holding + */ +function getAgentHeldAsset(agent) { + const agentId = agent.id || "default"; + const assetId = agentHeldAssets.get(agentId); + if (!assetId) return null; + return assets.find(a => a.id === assetId) || null; +} + +/** + * Get the interactable asset at the player's crosshair (center of screen). + * Returns { asset, actions, dist, canPickUp } if found, or null if nothing interactable. + */ +const _hintRayOrigin = new THREE.Vector3(); +const _hintRayDir = new THREE.Vector3(); +const _hintTmpSphere = new THREE.Sphere(); +const _hintRay = new THREE.Ray(); +const _cachedSphereCenter = new THREE.Vector3(); + +// Get the world-space bounding sphere of an asset from its cached local data. +// This is O(1) — no vertex traversal, just one matrix-vector multiply. +function getAssetWorldSphere(obj, outSphere) { + const lc = obj.userData._localSphereCenter; + const lr = obj.userData._localSphereRadius; + if (lc && lr) { + _cachedSphereCenter.copy(lc); + obj.localToWorld(_cachedSphereCenter); + const scale = obj.matrixWorld.getMaxScaleOnAxis(); + outSphere.set(_cachedSphereCenter, lr * scale); + return true; + } + return false; +} + +function getInteractableAssetCandidatesAtCrosshair() { + if (!camera) return []; + camera.getWorldPosition(_hintRayOrigin); + camera.getWorldDirection(_hintRayDir); + _hintRay.set(_hintRayOrigin, _hintRayDir); + + const maxDist = PLAYER_INTERACT_DISTANCE + 0.8; + const candidates = []; + for (const child of assetsGroup.children) { + const aid = child.name?.startsWith("asset:") ? child.name.slice(6) : null; + if (!aid) continue; + if (!getAssetWorldSphere(child, _hintTmpSphere)) continue; + _hintTmpSphere.radius = Math.max(_hintTmpSphere.radius, 0.3); + const centerDist = _hintRayOrigin.distanceTo(_hintTmpSphere.center); + if (centerDist > maxDist + _hintTmpSphere.radius) continue; + const hitPoint = _hintRay.intersectSphere(_hintTmpSphere, _tmpV1); + if (!hitPoint) continue; + const d = _hintRayOrigin.distanceTo(hitPoint); + if (d > maxDist) continue; + const toCenter = _tmpV2.copy(_hintTmpSphere.center).sub(_hintRayOrigin).normalize(); + const aim = Math.max(0, _hintRayDir.dot(toCenter)); + const score = aim * 4.0 - d * 0.45; + candidates.push({ id: aid, dist: d, aim, score }); + } + candidates.sort((a, b) => (b.score - a.score) || (a.dist - b.dist)); + return candidates.slice(0, 6); +} + +function cycleInteractableTarget(step = 1) { + const candidates = getInteractableAssetCandidatesAtCrosshair(); + if (!Array.isArray(candidates) || candidates.length <= 1) return false; + const sig = candidates.map((c) => c.id).join("|"); + if (sig !== _crosshairInteractCycleSig) { + _crosshairInteractCycleSig = sig; + _crosshairInteractCycleIndex = 0; + } + const len = candidates.length; + _crosshairInteractCycleIndex = (_crosshairInteractCycleIndex + step + len) % len; + _crosshairInteractCandidates = candidates; + return true; +} + +function getInteractableAssetAtCrosshair() { + const candidates = getInteractableAssetCandidatesAtCrosshair(); + const sig = candidates.map((c) => c.id).join("|"); + if (sig !== _crosshairInteractCycleSig) { + _crosshairInteractCycleSig = sig; + _crosshairInteractCycleIndex = 0; + } + _crosshairInteractCandidates = candidates; + const primary = candidates[_crosshairInteractCycleIndex] || null; + + if (!primary) { + // Fallback: pickable grouped shape assets + _playerInteractRaycaster.setFromCamera({ x: 0, y: 0 }, camera); + const hits = _playerInteractRaycaster.intersectObjects(primitivesGroup.children, false); + for (const hit of hits) { + if (hit.distance > PLAYER_INTERACT_DISTANCE + 0.5) continue; + const name = hit.object?.name || ""; + const m = name.match(/^prim:(.+)$/); + if (!m) continue; + const primId = m[1]; + const g = groups.find((gr) => (gr.children || []).includes(primId) && gr.pickable); + if (!g) continue; + const canPickUp = !playerHeldAsset && !playerHeldGroupId && !isGroupHeld(g.id).held; + return { kind: "group", group: g, actions: [], dist: hit.distance, canPickUp }; + } + return null; + } + + const asset = assets.find((a) => a.id === primary.id); + if (!asset) return null; + + const currentState = asset.currentStateId || asset.currentState || "A"; + const actions = (asset.actions || []).filter((act) => act.from === currentState); + const holdStatus = isAssetHeld(primary.id); + const canPickUp = asset.pickable && !holdStatus.held && !playerHeldAsset && !playerHeldGroupId; + + if (actions.length === 0 && !canPickUp) return null; + + return { + kind: "asset", + asset, + actions, + dist: primary.dist, + canPickUp, + candidateIndex: _crosshairInteractCycleIndex, + candidateCount: candidates.length, + }; +} + +/** + * Create or get the interaction popup element + */ +function getInteractionPopup() { + if (_interactionPopup) return _interactionPopup; + + _interactionPopup = document.createElement("div"); + _interactionPopup.id = "interaction-popup"; + // Styles are now in CSS, just set display none initially + _interactionPopup.style.display = "none"; + document.body.appendChild(_interactionPopup); + return _interactionPopup; +} + +/** + * Show the interaction popup with available actions + */ +function showInteractionPopup(asset, actions) { + const popup = getInteractionPopup(); + + // Build popup content + const title = asset.title || "(asset)"; + const stateObj = Array.isArray(asset.states) + ? asset.states.find((s) => s.id === (asset.currentStateId || asset.currentState)) + : null; + const stateName = stateObj?.name || ""; + + let html = `
${escapeHtml(title)}${stateName ? ` · ${escapeHtml(stateName)}` : ""}
`; + + actions.forEach((act, idx) => { + html += ``; + }); + + html += `
Press 1-${actions.length} or click · Esc to cancel
`; + + popup.innerHTML = html; + popup.style.display = "flex"; + _currentInteractableAsset = { asset, actions }; + + // Add click handlers to buttons + popup.querySelectorAll(".interact-action-btn").forEach((btn) => { + btn.addEventListener("click", async (e) => { + e.stopPropagation(); + const actionId = btn.getAttribute("data-action-id"); + + // Hide popup first + hideInteractionPopup(); + + // Execute the action + if (actionId === "__PICK_UP__") { + playerPickUpAsset(asset.id); + } else { + await executePlayerInteraction(asset.id, actionId); + } + + // Re-lock pointer after a short delay (click events can re-lock) + setTimeout(() => { + try { + controls?.lock?.(); + } catch (err) { + // Ignore + } + }, 50); + }); + // Hover effects handled in CSS + }); +} + +/** + * Hide the interaction popup + */ +function hideInteractionPopup() { + if (_interactionPopup) { + _interactionPopup.style.display = "none"; + } + _currentInteractableAsset = null; +} + +/** + * Check if interaction popup is visible + */ +function isInteractionPopupVisible() { + return _interactionPopup?.style.display === "flex"; +} + +/** + * Execute a player interaction with an asset + */ +async function executePlayerInteraction(assetId, actionId) { + // Handle special pick up action + if (actionId === "__PICK_UP__") { + const result = playerPickUpAsset(assetId); + return result.ok; + } + + const asset = assets.find((a) => a.id === assetId); + if (!asset) { + setStatus("Asset not found."); + return false; + } + + const action = (asset.actions || []).find((a) => a.id === actionId); + if (!action) { + setStatus("Action not available."); + return false; + } + + const ok = await applyAssetAction(assetId, actionId); + if (ok) { + setStatus(`${action.label || "Interacted"}: ${asset.title || "asset"}`); + } else { + setStatus("Interaction failed."); + } + return ok; +} + +/** + * Handle player interaction attempt (click or E key) + */ +async function handlePlayerInteraction() { + // If popup is already showing, do nothing (let popup handle it) + if (isInteractionPopupVisible()) { + return; + } + + // First, check if player is holding something - pressing E drops it + if (playerHeldAsset) { + playerDropAsset(); + return; + } + if (playerHeldGroupId) { + playerDropGroup(); + return; + } + + const target = getInteractableAssetAtCrosshair(); + if (!target) { + // No interactable asset at crosshair + return; + } + + const { kind, asset, group, actions, dist, canPickUp } = target; + if (kind === "group") { + if (canPickUp) playerPickUpGroup(group.id); + return; + } + + // Build combined action list (regular actions + pick up if available) + const combinedActions = [...actions]; + if (canPickUp) { + combinedActions.push({ id: "__PICK_UP__", label: "Pick up", special: true }); + } + + if (combinedActions.length === 1) { + // Single action - execute immediately + if (combinedActions[0].id === "__PICK_UP__") { + playerPickUpAsset(asset.id); + } else { + await executePlayerInteraction(asset.id, combinedActions[0].id); + } + } else if (combinedActions.length > 1) { + // Multiple actions - show selection popup + // Temporarily unlock pointer to allow clicking popup + controls?.unlock?.(); + showInteractionPopup(asset, combinedActions); + } +} + +// ============================================================================ +// END PLAYER INTERACTION SYSTEM +// ============================================================================ + +function deleteSelectedAsset() { + const a = getSelectedAsset(); + if (!a) return; + // remove collider + if (a._colliderHandle != null) { + try { + rapierWorld?.removeCollider?.(a._colliderHandle, true); + } catch {} + } + // remove visual + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (obj?.parent) obj.parent.remove(obj); + _assetBumpVelocities.delete(a.id); + assets = assets.filter((x) => x.id !== a.id); + selectedAssetId = null; + saveTagsForWorld(); + renderAssetsList(); + setStatus("Asset deleted."); +} + +async function interactSelectedAssetDebug() { + const a = getSelectedAsset(); + if (!a) return; + const state = a.currentStateId || a.currentState || "A"; + const outgoing = (a.actions || []).filter((x) => x.from === state); + const act = outgoing[0] || null; + if (!act) { + setStatus("Selected asset has no valid action from its current state."); + return; + } + const ok = await applyAssetAction(a.id, act.id); + setStatus(ok ? `Asset interacted: ${act.label}` : "Asset interaction failed."); +} + +function _newId(prefix) { + return `${prefix}${Date.now().toString(16)}${Math.random().toString(16).slice(2, 6)}`; +} + +function _cloneAssetWithFreshIds(src) { + // Ensure latest schema and interactions exist + const a = normalizeAsset(structuredClone ? structuredClone(src) : JSON.parse(JSON.stringify(src))); + + const newAssetId = _newId("asset_"); + const stateIdMap = new Map(); + const newStates = []; + + for (const s of a.states || []) { + const newSid = _newId("s"); + stateIdMap.set(s.id, newSid); + newStates.push({ + id: newSid, + name: s.name || "", + glbName: s.glbName || "", + dataBase64: s.dataBase64 || "", + interactions: [], + }); + } + + // Copy interactions (and rewrite state ids) + for (const s of a.states || []) { + const fromNew = stateIdMap.get(s.id); + const dstState = newStates.find((x) => x.id === fromNew); + if (!dstState) continue; + const ints = Array.isArray(s.interactions) ? s.interactions : []; + dstState.interactions = ints + .map((it) => ({ + id: _newId("it_"), + label: it.label || "toggle", + to: stateIdMap.get(it.to) || stateIdMap.get(s.id) || fromNew, + })) + .filter((it) => it.to && it.to !== fromNew); + } + + const curOld = a.currentStateId || a.currentState || (a.states?.[0]?.id ?? null); + const curNew = stateIdMap.get(curOld) || newStates[0]?.id || null; + + // Offset placement slightly forward so it doesn't overlap + let transform = a.transform ? structuredClone(a.transform) : null; + const obj = assetsGroup.getObjectByName(`asset:${src.id}`); + if (!transform) { + transform = { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } }; + } + if (obj) { + transform.position = { x: obj.position.x, y: obj.position.y, z: obj.position.z }; + transform.rotation = { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }; + transform.scale = { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }; + } else { + transform.position = transform.position || { x: 0, y: 0, z: 0 }; + transform.rotation = transform.rotation || { x: 0, y: 0, z: 0 }; + transform.scale = transform.scale || { x: 1, y: 1, z: 1 }; + } + const fwd = new THREE.Vector3(); + camera.getWorldDirection(fwd); + const offset = 0.7; + transform.position.x += fwd.x * offset; + transform.position.y += fwd.y * offset; + transform.position.z += fwd.z * offset; + + const duplicated = { + id: newAssetId, + title: (src.title || "").trim() ? `${src.title} (copy)` : "Asset (copy)", + notes: src.notes || "", + states: newStates, + currentStateId: curNew, + transform, + actions: [], + _colliderHandle: null, + }; + + // Build actions from interactions for runtime/agent/debug use + duplicated.actions = []; + for (const s of duplicated.states) { + for (const it of s.interactions || []) { + duplicated.actions.push({ id: it.id, label: it.label, from: s.id, to: it.to }); + } + } + + return duplicated; +} + +async function duplicateSelectedAsset() { + const a = getSelectedAsset(); + if (!a) return; + const dup = _cloneAssetWithFreshIds(a); + assets.push(dup); + saveTagsForWorld(); + await instantiateAsset(dup); + renderAssetsList(); + selectAsset(dup.id); + setStatus("Asset duplicated."); +} + +async function buildRapierTriMeshColliderFromObject(obj) { + await ensureRapierLoaded(); + const verts = []; + const indices = []; + let vertBase = 0; + const tmpPos = new THREE.Vector3(); + obj.updateMatrixWorld(true); + + obj.traverse((m) => { + if (!m.isMesh) return; + const geom = m.geometry; + const posAttr = geom?.attributes?.position; + if (!posAttr) return; + const indexAttr = geom.index; + const matWorld = m.matrixWorld; + + for (let i = 0; i < posAttr.count; i++) { + tmpPos.fromBufferAttribute(posAttr, i).applyMatrix4(matWorld); + verts.push(tmpPos.x, tmpPos.y, tmpPos.z); + } + + if (indexAttr) { + for (let i = 0; i < indexAttr.count; i++) indices.push(indexAttr.getX(i) + vertBase); + } else { + for (let i = 0; i < posAttr.count; i++) indices.push(vertBase + i); + } + vertBase += posAttr.count; + }); + + if (verts.length < 9 || indices.length < 3) return null; + const desc = RAPIER.ColliderDesc.trimesh(verts, indices).setFriction(0.9); + return rapierWorld.createCollider(desc); +} + +async function rebuildAssetCollider(assetId) { + const a = assets.find((x) => x.id === assetId); + if (!a) return; + await ensureRapierLoaded(); + if (!rapierWorld || !RAPIER) return; + + // Remove existing collider + if (a._colliderHandle != null) { + try { + if (typeof a._colliderHandle === 'object' && a._colliderHandle.handle !== undefined) { + rapierWorld.removeCollider(a._colliderHandle, true); + } + } catch (e) { + console.warn(`[COLLIDER] Failed to remove collider for ${assetId}:`, e); + } + a._colliderHandle = null; + } + + const obj = assetsGroup.getObjectByName(`asset:${assetId}`); + if (!obj) return; + const collider = await buildRapierTriMeshColliderFromObject(obj); + if (collider) { + a._colliderHandle = collider; + } +} + +function removeAssetColliderHandle(asset) { + if (!asset || asset._colliderHandle == null || !rapierWorld) return; + try { + if (typeof asset._colliderHandle === "object" && asset._colliderHandle.handle !== undefined) { + rapierWorld.removeCollider(asset._colliderHandle, true); + } + } catch (e) { + console.warn(`[COLLIDER] Failed to remove collider for ${asset.id}:`, e); + } + asset._colliderHandle = null; +} + +async function rebuildAssets() { + while (assetsGroup.children.length) assetsGroup.remove(assetsGroup.children[0]); + for (const a of assets) { + try { + await instantiateAsset(a); + } catch (e) { + console.warn("Failed to rebuild asset", a?.glb?.name, e); + } + } + selectAsset(selectedAssetId); +} + +// ============================================================================= +// PRIMITIVES – Parametric Shape System (Level Editor) +// ============================================================================= + +function createPrimitiveGeometry(type, dims) { + dims = dims || {}; + const num = (v, fallback) => { + const n = Number(v); + return Number.isFinite(n) ? n : fallback; + }; + const degToRad = (deg, fallback = 0) => (Number.isFinite(deg) ? deg : fallback) * Math.PI / 180; + const clampInt = (v, fallback, min = 1) => Math.max(min, Math.floor(Number(v) || fallback)); + const clamp01 = (v, fallback = 0) => { + const n = Number(v); + if (!Number.isFinite(n)) return fallback; + return Math.max(0, Math.min(1, n)); + }; + switch (type) { + case "box": { + const width = Math.max(0.01, num(dims.width, 1)); + const height = Math.max(0.01, num(dims.height, 1)); + const depth = Math.max(0.01, num(dims.depth, 1)); + const edgeRadius = Math.max(0, num(dims.edgeRadius, 0)); + if (edgeRadius > 0) { + const radius = Math.min(edgeRadius, width * 0.5, height * 0.5, depth * 0.5); + const edgeSegments = clampInt(dims.edgeSegments, 4, 1); + return new RoundedBoxGeometry(width, height, depth, edgeSegments, radius); + } + return new THREE.BoxGeometry( + width, + height, + depth, + clampInt(dims.widthSegments, 1, 1), + clampInt(dims.heightSegments, 1, 1), + clampInt(dims.depthSegments, 1, 1) + ); + } + case "sphere": + return new THREE.SphereGeometry( + Math.max(0.01, num(dims.radius, 0.5)), + clampInt(dims.widthSegments, 32, 3), + clampInt(dims.heightSegments, 16, 2), + degToRad(num(dims.phiStartDeg, 0), 0), + degToRad(num(dims.phiLengthDeg, 360), 360), + degToRad(num(dims.thetaStartDeg, 0), 0), + degToRad(num(dims.thetaLengthDeg, 180), 180) + ); + case "cylinder": + return new THREE.CylinderGeometry( + Math.max(0.01, num(dims.radiusTop, 0.5)), + Math.max(0.01, num(dims.radiusBottom, 0.5)), + Math.max(0.01, num(dims.height, 1)), + clampInt(dims.radialSegments, 32, 3), + clampInt(dims.heightSegments, 1, 1), + clamp01(dims.openEnded, 0) >= 0.5 + ); + case "cone": + return new THREE.ConeGeometry( + Math.max(0.01, num(dims.radius, 0.5)), + Math.max(0.01, num(dims.height, 1)), + clampInt(dims.radialSegments, 32, 3), + clampInt(dims.heightSegments, 1, 1), + clamp01(dims.openEnded, 0) >= 0.5 + ); + case "torus": + return new THREE.TorusGeometry( + Math.max(0.01, num(dims.radius, 0.5)), + Math.max(0.01, num(dims.tube, 0.15)), + clampInt(dims.radialSegments, 16, 3), + clampInt(dims.tubularSegments, 48, 3), + degToRad(num(dims.arcDeg, 360), 360) + ); + case "plane": + return new THREE.PlaneGeometry( + Math.max(0.01, num(dims.width, 2)), + Math.max(0.01, num(dims.height, 2)), + clampInt(dims.widthSegments, 1, 1), + clampInt(dims.heightSegments, 1, 1) + ); + default: + return new THREE.BoxGeometry(1, 1, 1); + } +} + +const _textureLoader = new THREE.TextureLoader(); +const _textureCache = new Map(); // dataUrl → THREE.Texture + +function createPrimitiveMaterial(mat) { + mat = mat || {}; + const uv = mat.uvTransform || {}; + const clamp01 = (v, fallback = 0) => { + const n = Number(v); + if (!Number.isFinite(n)) return fallback; + return Math.max(0, Math.min(1, n)); + }; + const hardness = clamp01(mat.hardness, 0); + const fluffiness = clamp01(mat.fluffiness, 0); + const params = { + color: new THREE.Color(mat.color || "#808080"), + roughness: mat.softness ?? mat.roughness ?? 0.7, + metalness: mat.metalness ?? 0.0, + specularIntensity: mat.specularIntensity ?? 1.0, + specularColor: new THREE.Color(mat.specularColor || "#ffffff"), + envMapIntensity: mat.envMapIntensity ?? 1.0, + opacity: mat.opacity ?? 1.0, + transparent: (mat.opacity ?? 1.0) < 1 || (mat.transmission ?? 0) > 0, + transmission: mat.transmission ?? 0.0, + ior: mat.ior ?? 1.45, + thickness: mat.thickness ?? 0.0, + attenuationColor: new THREE.Color(mat.attenuationColor || "#ffffff"), + attenuationDistance: Math.max(0.01, mat.attenuationDistance ?? 1.0), + iridescence: mat.iridescence ?? 0.0, + iridescenceIOR: mat.ior ?? 1.45, + emissive: new THREE.Color(mat.emissive || "#000000"), + emissiveIntensity: mat.emissiveIntensity ?? 0.0, + clearcoat: Math.max(mat.clearcoat ?? 0.0, hardness * 0.85), + clearcoatRoughness: Math.min(mat.clearcoatRoughness ?? 0.0, 1 - hardness * 0.8), + sheen: fluffiness, + sheenRoughness: 0.9, + sheenColor: new THREE.Color(mat.sheenColor || mat.color || "#808080"), + side: mat.doubleSided === false ? THREE.FrontSide : THREE.DoubleSide, + flatShading: mat.flatShading === true, + wireframe: mat.wireframe === true, + alphaTest: clamp01(mat.alphaCutoff, 0), + depthWrite: (mat.opacity ?? 1.0) >= 1 && (mat.transmission ?? 0) <= 0, + }; + if (mat.textureDataUrl) { + let baseTex = _textureCache.get(mat.textureDataUrl); + if (!baseTex) { + baseTex = _textureLoader.load(mat.textureDataUrl); + baseTex.colorSpace = THREE.SRGBColorSpace; + baseTex.wrapS = baseTex.wrapT = THREE.RepeatWrapping; + _textureCache.set(mat.textureDataUrl, baseTex); + } + const tex = baseTex.clone(); + tex.needsUpdate = true; + tex.repeat.set(uv.repeatX ?? 1, uv.repeatY ?? 1); + tex.offset.set(uv.offsetX ?? 0, uv.offsetY ?? 0); + tex.rotation = ((uv.rotationDeg ?? 0) * Math.PI) / 180; + tex.center.set(0.5, 0.5); + const textureSoftness = clamp01(mat.textureSoftness, 0.25); + const textureHardness = clamp01(mat.textureHardness, 0.5); + const maxAniso = renderer?.capabilities?.getMaxAnisotropy?.() || 1; + const targetAniso = Math.max(1, Math.round(1 + textureHardness * (maxAniso - 1))); + tex.anisotropy = Math.max(1, Math.round(targetAniso * (1 - textureSoftness * 0.85))); + tex.minFilter = textureSoftness > 0.6 ? THREE.LinearMipmapLinearFilter : THREE.LinearMipmapNearestFilter; + tex.magFilter = textureSoftness > 0.75 ? THREE.LinearFilter : (textureHardness > 0.9 ? THREE.NearestFilter : THREE.LinearFilter); + tex.generateMipmaps = true; + params.map = tex; + } + return new THREE.MeshPhysicalMaterial(params); +} + +function sanitizePrimitiveCutouts(cutouts) { + if (!Array.isArray(cutouts)) return []; + const out = []; + for (const c of cutouts) { + if (!c || typeof c !== "object") continue; + if (!Array.isArray(c.targetToSourceMatrix) || c.targetToSourceMatrix.length !== 16) continue; + const type = String(c.type || ""); + if (!["box", "sphere", "cylinder", "cone", "torus"].includes(type)) continue; + out.push({ + type, + targetToSourceMatrix: c.targetToSourceMatrix.map((n) => Number(n) || 0), + dimensions: { ...(c.dimensions || {}) }, + }); + if (out.length >= 8) break; + } + return out; +} + +function applyPrimitiveCutoutShader(mesh, primData) { + if (!mesh?.material?.isMeshPhysicalMaterial) return; + const cutouts = sanitizePrimitiveCutouts(primData?.cutouts); + if (!cutouts.length) return; + const mat = mesh.material; + const maxCuts = 8; + const cutMatrices = Array.from({ length: maxCuts }, () => new THREE.Matrix4()); + const cutA = Array.from({ length: maxCuts }, () => new THREE.Vector4(0, 0, 0, 0)); + const cutB = Array.from({ length: maxCuts }, () => new THREE.Vector4(0, 0, 0, 0)); + const typeCodeFor = (t) => (t === "sphere" ? 1 : t === "box" ? 2 : t === "cylinder" ? 3 : t === "cone" ? 4 : t === "torus" ? 5 : 0); + for (let i = 0; i < cutouts.length && i < maxCuts; i++) { + const c = cutouts[i]; + cutMatrices[i].fromArray(c.targetToSourceMatrix); + const d = c.dimensions || {}; + switch (c.type) { + case "sphere": + cutA[i].set(Number(d.radius) || 0.5, 0, 0, typeCodeFor(c.type)); + break; + case "box": + cutA[i].set((Number(d.width) || 1) * 0.5, (Number(d.height) || 1) * 0.5, (Number(d.depth) || 1) * 0.5, typeCodeFor(c.type)); + break; + case "cylinder": + case "cone": + cutA[i].set(Math.max(Number(d.radiusTop) || Number(d.radius) || 0.5, Number(d.radiusBottom) || Number(d.radius) || 0.5), Number(d.height) || 1, 0, typeCodeFor(c.type)); + break; + case "torus": + cutA[i].set(Number(d.radius) || 0.5, Number(d.tube) || 0.15, 0, typeCodeFor(c.type)); + break; + default: + break; + } + } + mat.onBeforeCompile = (shader) => { + shader.uniforms.uCutoutCount = { value: cutouts.length }; + shader.uniforms.uCutoutInv = { value: cutMatrices }; + shader.uniforms.uCutoutA = { value: cutA }; + shader.uniforms.uCutoutB = { value: cutB }; + shader.vertexShader = ` +varying vec3 vPrimLocalPos; +${shader.vertexShader}`.replace( + "#include ", + `#include +vPrimLocalPos = position;` + ); + shader.fragmentShader = ` +uniform int uCutoutCount; +uniform mat4 uCutoutInv[8]; +uniform vec4 uCutoutA[8]; +uniform vec4 uCutoutB[8]; +varying vec3 vPrimLocalPos; + +float sdfBox(vec3 p, vec3 b) { + vec3 q = abs(p) - b; + return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); +} +float sdfSphere(vec3 p, float r) { return length(p) - r; } +float sdfCylinderY(vec3 p, float r, float h) { + vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, h * 0.5); + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); +} +float sdfTorus(vec3 p, float r, float t) { + vec2 q = vec2(length(p.xz) - r, p.y); + return length(q) - t; +} +${shader.fragmentShader}`.replace( + "#include ", + `#include +for (int i = 0; i < 8; i++) { + if (i >= uCutoutCount) break; + vec3 lp = (uCutoutInv[i] * vec4(vPrimLocalPos, 1.0)).xyz; + float typ = uCutoutA[i].w; + float d = 1e6; + if (typ < 1.5) d = sdfSphere(lp, uCutoutA[i].x); + else if (typ < 2.5) d = sdfBox(lp, vec3(uCutoutA[i].x, uCutoutA[i].y, uCutoutA[i].z)); + else if (typ < 3.5) d = sdfCylinderY(lp, uCutoutA[i].x, uCutoutA[i].y); + else if (typ < 4.5) d = sdfCylinderY(lp, uCutoutA[i].x, uCutoutA[i].y); + else d = sdfTorus(lp, uCutoutA[i].x, uCutoutA[i].y); + if (d < 0.0) discard; +}` + ); + }; + mat.customProgramCacheKey = () => `cutouts:${cutouts.length}:${cutouts.map((c) => c.type).join(",")}`; + mat.needsUpdate = true; +} + +function disposePrimitiveMaterial(material) { + if (!material) return; + const mats = Array.isArray(material) ? material : [material]; + for (const m of mats) { + if (!m) continue; + const maps = [ + "map", + "alphaMap", + "aoMap", + "normalMap", + "roughnessMap", + "metalnessMap", + "emissiveMap", + "clearcoatMap", + "clearcoatRoughnessMap", + "transmissionMap", + "thicknessMap", + ]; + for (const key of maps) { + const tex = m[key]; + if (tex?.isTexture) tex.dispose(); + } + m.dispose?.(); + } +} + +// Deferred collider queue — colliders are only created at a safe frame boundary +const _pendingColliderBuilds = []; + +function flushPendingColliderBuilds() { + if (!rapierWorld || !worldBody || _pendingColliderBuilds.length === 0) return; + while (_pendingColliderBuilds.length > 0) { + const prim = _pendingColliderBuilds.shift(); + // Verify the primitive still exists and still wants physics + if (primitives.includes(prim) && prim.physics !== false) { + rebuildPrimitiveColliderSync(prim); + } + } +} + +function instantiatePrimitive(prim) { + // Remove existing + const existing = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (existing) { + existing.geometry?.dispose(); + disposePrimitiveMaterial(existing.material); + primitivesGroup.remove(existing); + } + + const geom = createPrimitiveGeometry(prim.type, prim.dimensions); + const mat = createPrimitiveMaterial(prim.material); + const mesh = new THREE.Mesh(geom, mat); + applyPrimitiveCutoutShader(mesh, prim); + mesh.name = `prim:${prim.id}`; + mesh.userData.primitiveId = prim.id; + mesh.userData.isPrimitive = true; + // Default both to true — shapes should always participate in shadows + mesh.castShadow = prim.castShadow !== false; + mesh.receiveShadow = prim.receiveShadow !== false; + + const tr = prim.transform || {}; + if (tr.position) mesh.position.set(tr.position.x, tr.position.y, tr.position.z); + if (tr.rotation) mesh.rotation.set(tr.rotation.x, tr.rotation.y, tr.rotation.z); + if (tr.scale) mesh.scale.set(tr.scale.x ?? 1, tr.scale.y ?? 1, tr.scale.z ?? 1); + + primitivesGroup.add(mesh); + + // Build collider — if Rapier is ready, do it now; otherwise queue it + if (prim.physics !== false) { + if (rapierWorld && worldBody) { + rebuildPrimitiveColliderSync(prim); + } else { + // Queue for deferred build once Rapier is ready + _pendingColliderBuilds.push(prim); + // Kick off Rapier init (non-blocking, collider will be built by flush) + ensureRapierLoaded(); + } + } +} + +// Safely remove a primitive's existing collider from the Rapier world +function removePrimitiveCollider(prim) { + if (prim._colliderHandle == null || !rapierWorld) return; + try { + if (typeof prim._colliderHandle === "object" && prim._colliderHandle.handle !== undefined) { + rapierWorld.removeCollider(prim._colliderHandle, true); + } + } catch (e) { + console.warn(`[COLLIDER] Primitive collider remove failed for ${prim.id}:`, e); + } + prim._colliderHandle = null; +} + +// SYNCHRONOUS collider creation for native Rapier shapes. +// Only falls back to async for trimesh (torus, plane). +function rebuildPrimitiveColliderSync(prim) { + if (!prim) return; + // Rapier must already be loaded for sync creation + if (!rapierWorld || !RAPIER || !worldBody) return; + + removePrimitiveCollider(prim); + if (prim.physics === false) return; + + const mesh = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (!mesh) return; + + const dims = prim.dimensions || {}; + const s = prim.transform?.scale || { x: 1, y: 1, z: 1 }; + const pos = prim.transform?.position || { x: 0, y: 0, z: 0 }; + const rot = prim.transform?.rotation || { x: 0, y: 0, z: 0 }; + + // Clamp all half-extents / radii to a safe minimum to avoid WASM traps + const clamp = (v) => Math.max(v, 0.001); + + let desc = null; + + // Use native Rapier collision shapes – far more compute-efficient than trimesh + switch (prim.type) { + case "box": + desc = RAPIER.ColliderDesc.cuboid( + clamp(((dims.width || 1) * (s.x ?? 1)) / 2), + clamp(((dims.height || 1) * (s.y ?? 1)) / 2), + clamp(((dims.depth || 1) * (s.z ?? 1)) / 2) + ); + break; + case "sphere": + desc = RAPIER.ColliderDesc.ball( + clamp((dims.radius || 0.5) * Math.max(s.x ?? 1, s.y ?? 1, s.z ?? 1)) + ); + break; + case "cylinder": + desc = RAPIER.ColliderDesc.cylinder( + clamp(((dims.height || 1) * (s.y ?? 1)) / 2), + clamp(Math.max(dims.radiusTop ?? 0.5, dims.radiusBottom ?? 0.5) * Math.max(s.x ?? 1, s.z ?? 1)) + ); + break; + case "cone": + desc = RAPIER.ColliderDesc.cone( + clamp(((dims.height || 1) * (s.y ?? 1)) / 2), + clamp((dims.radius || 0.5) * Math.max(s.x ?? 1, s.z ?? 1)) + ); + break; + case "plane": { + // PlaneGeometry lies in the XY plane (normal along +Z), so make the + // cuboid thin in Z to match the visual exactly. No rotation offset needed. + const pw = clamp(((dims.width || 2) * (s.x ?? 1)) / 2); + const ph = clamp(((dims.height || 2) * (s.y ?? 1)) / 2); + desc = RAPIER.ColliderDesc.cuboid(pw, ph, 0.005); + break; // fall through to the standard rotation/translation below + } + case "torus": { + // Torus: use trimesh async fallback (deferred, won't block) + rebuildPrimitiveColliderAsync(prim); + return; + } + default: + return; + } + + if (desc) { + desc.setTranslation(pos.x, pos.y, pos.z); + const euler = new THREE.Euler(rot.x, rot.y, rot.z); + const quat = new THREE.Quaternion().setFromEuler(euler); + desc.setRotation({ x: quat.x, y: quat.y, z: quat.z, w: quat.w }); + desc.setFriction(0.9); + try { + const collider = rapierWorld.createCollider(desc); + prim._colliderHandle = collider; + } catch (e) { + console.warn(`[COLLIDER] Failed to create primitive collider for ${prim.type}:`, e); + } + } +} + +// Async fallback only used for torus (trimesh) +async function rebuildPrimitiveColliderAsync(prim) { + if (!prim) return; + await ensureRapierLoaded(); + if (!rapierWorld || !RAPIER || !worldBody) return; + removePrimitiveCollider(prim); + if (prim.physics === false) return; + const mesh = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (!mesh) return; + try { + const collider = await buildRapierTriMeshColliderFromObject(mesh); + if (collider) prim._colliderHandle = collider; + } catch (e) { + console.warn(`[COLLIDER] Trimesh fallback failed for ${prim.id}:`, e); + } +} + +// Keep old name as alias for callers (e.g. dimension/transform change handlers) +function rebuildPrimitiveCollider(primId) { + const prim = primitives.find((p) => p.id === primId); + if (prim) rebuildPrimitiveColliderSync(prim); +} + +function addPrimitiveAtCrosshair(type) { + const spawnPos = getPlacementAtCrosshair({ raycastDistance: 250, surfaceOffset: 0.5 }).position; + addPrimitiveAtPosition(type, spawnPos); +} + +function addPrimitiveAtPosition(type, spawnPos) { + const prim = { + id: randId(), + type, + name: type.charAt(0).toUpperCase() + type.slice(1), + notes: "", + tags: [], // string tags for filtering / grouping + state: "static", // static | dynamic | interactable | trigger | decoration + metadata: {}, // arbitrary key-value pairs + dimensions: { ...(PRIMITIVE_DEFAULTS[type] || PRIMITIVE_DEFAULTS.box) }, + transform: { + position: spawnPos, + rotation: { x: 0, y: 0, z: 0 }, + scale: { x: 1, y: 1, z: 1 }, + }, + material: { + color: "#808080", + roughness: 0.7, + softness: 0.7, + hardness: 0.0, + fluffiness: 0.0, + metalness: 0.0, + specularIntensity: 1.0, + specularColor: "#ffffff", + envMapIntensity: 1.0, + opacity: 1.0, + transmission: 0.0, + ior: 1.45, + thickness: 0.0, + attenuationColor: "#ffffff", + attenuationDistance: 1.0, + iridescence: 0.0, + emissive: "#000000", + emissiveIntensity: 0.0, + clearcoat: 0.0, + clearcoatRoughness: 0.0, + alphaCutoff: 0.0, + textureSoftness: 0.25, + textureHardness: 0.5, + doubleSided: true, + flatShading: false, + wireframe: false, + uvTransform: { repeatX: 1, repeatY: 1, offsetX: 0, offsetY: 0, rotationDeg: 0 }, + textureDataUrl: null, + }, + physics: true, + castShadow: true, + receiveShadow: true, + }; + + primitives.push(prim); + instantiatePrimitive(prim); + saveTagsForWorld(); + renderPrimitivesList(); + selectPrimitive(prim.id); + setStatus(`${prim.name} placed. Use transform tools to position.`); +} + +function selectPrimitive(id) { + selectedPrimitiveId = id; + if (id) { + selectedAssetId = null; + } + renderPrimitivesList(); +} + +function getPlacementAtCrosshair({ raycastDistance = 500, fallbackDistance = 3, surfaceOffset = 0.02 } = {}) { + const hit = rapierRaycastFromCamera(raycastDistance); + if (hit) { + const n = hit.normal + ? new THREE.Vector3(hit.normal.x, hit.normal.y, hit.normal.z).normalize() + : new THREE.Vector3(0, 1, 0); + return { + hit: true, + point: { x: hit.point.x, y: hit.point.y, z: hit.point.z }, + normal: { x: n.x, y: n.y, z: n.z }, + position: { + x: hit.point.x + n.x * surfaceOffset, + y: hit.point.y + n.y * surfaceOffset, + z: hit.point.z + n.z * surfaceOffset, + }, + }; + } + + // If no collider is hit, place directly in front of the crosshair. + const dir = camera.getWorldDirection(new THREE.Vector3()); + const p = camera.getWorldPosition(new THREE.Vector3()); + return { + hit: false, + point: { + x: p.x + dir.x * fallbackDistance, + y: p.y + dir.y * fallbackDistance, + z: p.z + dir.z * fallbackDistance, + }, + normal: { x: 0, y: 1, z: 0 }, + position: { + x: p.x + dir.x * fallbackDistance, + y: p.y + dir.y * fallbackDistance, + z: p.z + dir.z * fallbackDistance, + }, + }; +} + +function getPlacementFromAgentView(agent, { raycastDistance = 500, fallbackDistance = 2.5, surfaceOffset = 0.02 } = {}) { + const [ax, ay, az] = agent?.getPosition?.() || [0, 0, 0]; + const yaw = agent?.group?.rotation?.y ?? 0; + const pitch = typeof agent?.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch); + const sp = Math.sin(pitch); + const dx = Math.sin(yaw) * cp; + const dy = sp; + const dz = Math.cos(yaw) * cp; + const eyeY = ay + PLAYER_EYE_HEIGHT * 0.9; + + if (rapierWorld && RAPIER) { + const ray = new RAPIER.Ray({ x: ax, y: eyeY, z: az }, { x: dx, y: dy, z: dz }); + const hit = rapierWorld.queryPipeline.castRayAndGetNormal( + rapierWorld.bodies, + rapierWorld.colliders, + ray, + raycastDistance, + false, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + agent?.collider?.handle + ); + if (hit) { + const toi = hit.toi ?? hit.timeOfImpact ?? 0; + const px = ax + dx * toi; + const py = eyeY + dy * toi; + const pz = az + dz * toi; + const n = hit.normal + ? new THREE.Vector3(hit.normal.x, hit.normal.y, hit.normal.z).normalize() + : new THREE.Vector3(0, 1, 0); + return { + hit: true, + point: { x: px, y: py, z: pz }, + normal: { x: n.x, y: n.y, z: n.z }, + position: { + x: px + n.x * surfaceOffset, + y: py + n.y * surfaceOffset, + z: pz + n.z * surfaceOffset, + }, + }; + } + } + + return { + hit: false, + point: { + x: ax + dx * fallbackDistance, + y: eyeY + dy * fallbackDistance, + z: az + dz * fallbackDistance, + }, + normal: { x: 0, y: 1, z: 0 }, + position: { + x: ax + dx * fallbackDistance, + y: eyeY + dy * fallbackDistance, + z: az + dz * fallbackDistance, + }, + }; +} + + +function getSelectedPrimitive() { + return primitives.find((p) => p.id === selectedPrimitiveId) || null; +} + +function buildPrimitiveCutoutFromSource(targetId, sourceId) { + const targetPrim = primitives.find((p) => p.id === targetId); + const sourcePrim = primitives.find((p) => p.id === sourceId); + if (!targetPrim || !sourcePrim) return null; + const targetMesh = primitivesGroup.getObjectByName(`prim:${targetId}`); + const sourceMesh = primitivesGroup.getObjectByName(`prim:${sourceId}`); + if (!targetMesh || !sourceMesh) return null; + targetMesh.updateMatrixWorld(true); + sourceMesh.updateMatrixWorld(true); + const targetWorldInv = new THREE.Matrix4().copy(targetMesh.matrixWorld).invert(); + const sourceInTarget = new THREE.Matrix4().multiplyMatrices(targetWorldInv, sourceMesh.matrixWorld); + const targetToSource = new THREE.Matrix4().copy(sourceInTarget).invert(); + return { + id: randId(), + type: sourcePrim.type, + targetToSourceMatrix: targetToSource.elements.slice(), + dimensions: { ...(sourcePrim.dimensions || {}) }, + }; +} + + +function getOverlappingPrimitiveIds(targetId) { + const targetMesh = primitivesGroup.getObjectByName(`prim:${targetId}`); + if (!targetMesh) return []; + const targetBox = new THREE.Box3().setFromObject(targetMesh).expandByScalar(0.01); + const out = []; + for (const p of primitives) { + if (p.id === targetId) continue; + const mesh = primitivesGroup.getObjectByName(`prim:${p.id}`); + if (!mesh) continue; + const box = new THREE.Box3().setFromObject(mesh); + if (box.isEmpty()) continue; + if (targetBox.intersectsBox(box)) out.push(p.id); + } + return out; +} + +function persistSelectedPrimitiveTransform() { + const prim = getSelectedPrimitive(); + if (!prim) return; + const obj = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (!obj) return; + prim.transform = { + position: { x: obj.position.x, y: obj.position.y, z: obj.position.z }, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + scale: { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z }, + }; + saveTagsForWorld(); + rebuildPrimitiveColliderSync(prim); +} + +function deletePrimitive(id) { + const idx = primitives.findIndex((p) => p.id === id); + if (idx === -1) return; + const prim = primitives[idx]; + + // Remove collider safely + removePrimitiveCollider(prim); + + // Remove visual + const obj = primitivesGroup.getObjectByName(`prim:${id}`); + if (obj) { + obj.geometry?.dispose(); + disposePrimitiveMaterial(obj.material); + primitivesGroup.remove(obj); + } + + primitives.splice(idx, 1); + if (selectedPrimitiveId === id) { + selectedPrimitiveId = null; + } + saveTagsForWorld(); + renderPrimitivesList(); + setStatus("Primitive deleted."); +} + +function duplicatePrimitive(id) { + const src = primitives.find((p) => p.id === id); + if (!src) return; + // Strip runtime objects (collider handle has circular refs) before deep clone + const { _colliderHandle, ...serializable } = src; + const clone = JSON.parse(JSON.stringify(serializable)); + clone.id = randId(); + clone.name = src.name + " copy"; + clone._colliderHandle = null; + // Offset slightly + if (clone.transform?.position) { + clone.transform.position.x += 1; + } + primitives.push(clone); + instantiatePrimitive(clone); + saveTagsForWorld(); + renderPrimitivesList(); + selectPrimitive(clone.id); + setStatus("Primitive duplicated."); +} + +function updatePrimitiveMaterial(primId) { + const prim = primitives.find((p) => p.id === primId); + if (!prim) return; + const mesh = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (!mesh) return; + disposePrimitiveMaterial(mesh.material); + mesh.material = createPrimitiveMaterial(prim.material); + applyPrimitiveCutoutShader(mesh, prim); +} + +function updatePrimitiveDimensions(primId) { + const prim = primitives.find((p) => p.id === primId); + if (!prim) return; + const mesh = primitivesGroup.getObjectByName(`prim:${prim.id}`); + if (!mesh) return; + mesh.geometry?.dispose(); + mesh.geometry = createPrimitiveGeometry(prim.type, prim.dimensions); + rebuildPrimitiveCollider(prim.id); +} + +function renderPrimitivesList() { + // No-op in sim-only mode (editor primitives list not present) +} + +function rebuildAllPrimitives() { + // Remove all existing colliders first + for (const p of primitives) { + removePrimitiveCollider(p); + } + // Remove all visual meshes + while (primitivesGroup.children.length) { + const c = primitivesGroup.children[0]; + c.geometry?.dispose(); + disposePrimitiveMaterial(c.material); + primitivesGroup.remove(c); + } + // Rebuild all + for (const p of primitives) { + try { + instantiatePrimitive(p); + } catch (e) { + console.warn("Failed to rebuild primitive", p.id, e); + } + } +} + +// ============================================================================= +// EDITOR LIGHTS – User-placed lights with visible proxy icons +// ============================================================================= + +function instantiateEditorLight(lightData) { + // Remove existing + removeEditorLightObjects(lightData.id); + + let lightObj; + const color = new THREE.Color(lightData.color || "#ffffff"); + const intensity = lightData.intensity ?? 1.0; + + switch (lightData.type) { + case "point": + lightObj = new THREE.PointLight(color, intensity, lightData.distance || 0); + break; + case "spot": { + lightObj = new THREE.SpotLight( + color, + intensity, + lightData.distance || 0, + lightData.angle ?? Math.PI / 4, + lightData.penumbra ?? 0.1 + ); + const tgt = lightData.target || { x: 0, y: 0, z: 0 }; + lightObj.target.position.set(tgt.x, tgt.y, tgt.z); + lightsGroup.add(lightObj.target); + break; + } + case "directional": + default: { + lightObj = new THREE.DirectionalLight(color, intensity); + const tgt = lightData.target || { x: 0, y: 0, z: 0 }; + lightObj.target.position.set(tgt.x, tgt.y, tgt.z); + lightsGroup.add(lightObj.target); + break; + } + } + + const pos = lightData.position || { x: 5, y: 10, z: 5 }; + lightObj.position.set(pos.x, pos.y, pos.z); + lightObj.castShadow = lightData.castShadow ?? false; + lightObj.name = `light:${lightData.id}`; + lightObj.userData.editorLightId = lightData.id; + lightObj.userData.isEditorLight = true; + + // Configure shadow map for this light (only renders when castShadow=true). + // Use 512 for point/spot (6-face cubemap = expensive) and 1024 for directional. + if (lightObj.shadow) { + const res = lightObj.isDirectionalLight ? 1024 : 512; + lightObj.shadow.mapSize.width = res; + lightObj.shadow.mapSize.height = res; + lightObj.shadow.bias = -0.003; + if (lightObj.shadow.camera) { + if (lightObj.isDirectionalLight) { + lightObj.shadow.camera.near = 0.5; + lightObj.shadow.camera.far = 50; + lightObj.shadow.camera.left = -20; + lightObj.shadow.camera.right = 20; + lightObj.shadow.camera.top = 20; + lightObj.shadow.camera.bottom = -20; + } else { + lightObj.shadow.camera.near = 0.5; + lightObj.shadow.camera.far = Math.min(lightData.distance || 30, 30); + } + } + } + + lightsGroup.add(lightObj); + lightData._lightObj = lightObj; + lightData._proxyObj = null; + lightData._helperObj = null; +} + +function removeEditorLightObjects(id) { + const names = [`light:${id}`, `lightHelper:${id}`, `lightProxy:${id}`]; + for (const n of names) { + const obj = lightsGroup.getObjectByName(n); + if (obj) { + // Remove target if it exists (directional/spot) + if (obj.target && obj.target.parent) obj.target.parent.remove(obj.target); + // Dispose children meshes + obj.traverse?.((c) => { + if (c.geometry) c.geometry.dispose(); + if (c.material) { + if (c.material.map) c.material.map.dispose(); + c.material.dispose(); + } + }); + lightsGroup.remove(obj); + } + } +} + + + +function rebuildAllEditorLights() { + // Remove all light objects + while (lightsGroup.children.length) { + const c = lightsGroup.children[0]; + c.traverse?.((m) => { m.geometry?.dispose(); m.material?.dispose(); }); + lightsGroup.remove(c); + } + for (const ld of editorLights) { + ld._lightObj = null; + ld._helperObj = null; + ld._proxyObj = null; + try { + instantiateEditorLight(ld); + } catch (e) { + console.warn("Failed to rebuild light", ld.id, e); + } + } + // Enable/disable the renderer shadow map based on whether any light casts shadows + syncShadowMapEnabled(); +} + +// ============================================================================= +// DETAILS PANEL & TRANSFORM XYZ – UE-style unified properties +// ============================================================================= + +const RAD2DEG = 180 / Math.PI; +const DEG2RAD = Math.PI / 180; + +// Dynamically enable/disable the shadow map system. +// When no light casts shadows, the renderer skips ALL shadow work (zero overhead). +function enforceShadowSamplerBudget() { + // Prevent WebGL shader validation failures: + // "texture image units count exceeds MAX_TEXTURE_IMAGE_UNITS" + // Point-light shadows are especially expensive (cube map = ~6 samplers). + const budget = 8; + const costFor = (lightObj) => (lightObj?.isPointLight ? 6 : 1); + + const candidates = []; + for (const sl of sceneLights) { + if (!sl?.obj || sl.obj.visible === false) continue; + if (!sl.obj.castShadow) continue; + candidates.push({ obj: sl.obj, source: "scene", meta: sl }); + } + for (const ld of editorLights) { + if (!ld?._lightObj || ld._lightObj.visible === false) continue; + if (!ld._lightObj.castShadow) continue; + candidates.push({ obj: ld._lightObj, source: "editor", meta: ld }); + } + + // Prefer non-point shadow lights first (directional/spot), then points. + candidates.sort((a, b) => { + const ac = costFor(a.obj); + const bc = costFor(b.obj); + if (ac !== bc) return ac - bc; // cheaper first + return 0; + }); + + let used = 0; + for (const c of candidates) { + const cost = costFor(c.obj); + if (used + cost <= budget) { + used += cost; + continue; + } + c.obj.castShadow = false; + // keep data model consistent so UI reflects actual runtime state + if (c.source === "editor") c.meta.castShadow = false; + } +} + +function syncShadowMapEnabled() { + enforceShadowSamplerBudget(); + let anyCast = false; + // Check scene lights + for (const sl of sceneLights) { + if (sl.obj?.castShadow && sl.obj?.visible !== false) { anyCast = true; break; } + } + // Check editor lights + if (!anyCast) { + for (const ld of editorLights) { + if (ld._lightObj?.castShadow && ld._lightObj?.visible !== false) { anyCast = true; break; } + } + } + if (renderer.shadowMap.enabled !== anyCast) { + renderer.shadowMap.enabled = anyCast; + // When toggling shadow maps, Three.js needs to recompile materials + scene.traverse((obj) => { if (obj.material) obj.material.needsUpdate = true; }); + } + if (anyCast) renderer.shadowMap.needsUpdate = true; +} + +function renderSceneInMode(mode) { + const savedOverride = scene.overrideMaterial; + const savedBg = scene.background; + const savedAssets = assetsGroup.visible; + const savedPrims = primitivesGroup.visible; + const savedLights = lightsGroup.visible; + const savedTags = tagsGroup.visible; + const savedLidar = lidarVizGroup.visible; + const savedOverlay = rgbdPcOverlayGroup.visible; + + if (mode === "rgb") { + scene.overrideMaterial = null; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + scene.background = DEFAULT_SCENE_BG; + renderer.render(scene, camera); + } else if (mode === "lidar") { + scene.overrideMaterial = null; + assetsGroup.visible = false; + primitivesGroup.visible = false; + lightsGroup.visible = false; + tagsGroup.visible = false; + lidarVizGroup.visible = true; + rgbdPcOverlayGroup.visible = rgbdPcOverlayOnLidar && _rgbdPcOverlayLastCount > 0; + scene.background = RGBD_BG; + renderer.render(scene, camera); + } + + scene.overrideMaterial = savedOverride; + scene.background = savedBg; + assetsGroup.visible = savedAssets; + primitivesGroup.visible = savedPrims; + lightsGroup.visible = savedLights; + tagsGroup.visible = savedTags; + lidarVizGroup.visible = savedLidar; + rgbdPcOverlayGroup.visible = savedOverlay; +} + +function renderCompareViews() { + const sz = renderer.getSize(new THREE.Vector2()); + const W = sz.x; + const H = sz.y; + const halfW = Math.floor(W / 2); + const halfH = Math.floor(H / 2); + + renderer.setScissorTest(true); + renderer.autoClear = false; + + renderer.setViewport(0, 0, W, H); + renderer.setScissor(0, 0, W, H); + renderer.setClearColor(0x000000, 1); + renderer.clear(true, true, true); + + // Top-left: RGB + renderer.setViewport(0, halfH, halfW, halfH); + renderer.setScissor(0, halfH, halfW, halfH); + renderer.setClearColor(DEFAULT_SCENE_BG, 1); + renderer.clear(true, true, true); + renderSceneInMode("rgb"); + + // Top-right: RGB-D + renderRgbdMetricPassOffscreen(); + rgbdVizMaterial.uniforms.uGrayMode.value = rgbdVizMode === "gray" ? 1.0 : 0.0; + renderer.setRenderTarget(null); + renderer.setViewport(halfW, halfH, W - halfW, halfH); + renderer.setScissor(halfW, halfH, W - halfW, halfH); + renderer.setClearColor(RGBD_BG, 1); + renderer.clear(true, true, true); + renderer.render(rgbdVizScene, rgbdPostCamera); + + // Bottom-center: LiDAR + const lidarX = Math.floor((W - halfW) / 2); + renderer.setViewport(lidarX, 0, halfW, halfH); + renderer.setScissor(lidarX, 0, halfW, halfH); + renderer.setClearColor(RGBD_BG, 1); + renderer.clear(true, true, true); + renderSceneInMode("lidar"); + + renderer.setScissorTest(false); + renderer.autoClear = true; + renderer.setViewport(0, 0, W, H); + renderer.setScissor(0, 0, W, H); +} + +function renderActiveView() { + syncShadowMapEnabled(); + if (simCompareView) { + renderCompareViews(); + } else if (simSensorViewMode === "rgbd") { + renderRgbdView(); + } else { + renderer.render(scene, camera); + } +} + +const _tmpCamPos = new THREE.Vector3(); +const _tmpCamDir = new THREE.Vector3(); +const _raycaster = new THREE.Raycaster(); + +function rapierRaycastFromCamera(maxToi = 250) { + if (!rapierWorld || !RAPIER) return null; + // Query pipeline is kept current by rapierWorld.step() in updateRapier + + const o = camera.getWorldPosition(_tmpCamPos); + const d = camera.getWorldDirection(_tmpCamDir).normalize(); + + const ray = new RAPIER.Ray({ x: o.x, y: o.y, z: o.z }, { x: d.x, y: d.y, z: d.z }); + const hit = rapierWorld.queryPipeline.castRayAndGetNormal( + rapierWorld.bodies, + rapierWorld.colliders, + ray, + maxToi, + false, // hollow: can hit boundary even if ray starts inside + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + playerCollider?.handle + ); + if (!hit) return null; + const toi = hit.toi ?? hit.timeOfImpact ?? 0; + const p = { x: o.x + d.x * toi, y: o.y + d.y * toi, z: o.z + d.z * toi }; + const n = hit.normal ? { x: hit.normal.x, y: hit.normal.y, z: hit.normal.z } : null; + return { point: p, normal: n, colliderHandle: hit.colliderHandle ?? null, toi }; +} + +function isShapeFreeAt(shape, rot, pos, excludeColliderHandle = null) { + if (!rapierWorld || !RAPIER) return false; + const hit = rapierWorld.queryPipeline.intersectionWithShape( + rapierWorld.bodies, + rapierWorld.colliders, + pos, + rot, + shape, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + excludeColliderHandle + ); + return hit == null; +} + +function findNearbyFreeSpotForCollider(collider, startPos, maxR = 2.0, step = 0.12) { + if (!collider) return null; + const shape = collider.shape; + const rot = collider.rotation(); + const exclude = collider.handle; + + if (isShapeFreeAt(shape, rot, startPos, exclude)) return startPos; + + const dirs = [ + [1, 0, 0], + [-1, 0, 0], + [0, 0, 1], + [0, 0, -1], + [1, 0, 1], + [1, 0, -1], + [-1, 0, 1], + [-1, 0, -1], + [0, 1, 0], + [0, -1, 0], + ]; + for (let r = step; r <= maxR; r += step) { + for (const [dx, dy, dz] of dirs) { + const len = Math.hypot(dx, dy, dz) || 1; + const pos = { x: startPos.x + (dx / len) * r, y: startPos.y + (dy / len) * r, z: startPos.z + (dz / len) * r }; + if (isShapeFreeAt(shape, rot, pos, exclude)) return pos; + } + } + return null; +} + +function removeAiAgent(agent, reason = "manual") { + if (!agent) return; + const removedId = String(agent.id || ""); + try { + aiAgents = aiAgents.filter((a) => a !== agent); + agentUiPush(`${new Date().toLocaleTimeString()}\nAGENT DESPAWN\n${agent.id} (${reason})`); + agent.dispose?.(); + } catch {} + if (removedId) { + _agentTasks.delete(removedId); + agentInspectorStateById.delete(removedId); + } + if (removedId) removeAgentBadge(removedId); + if (agentCameraFollowId === removedId) { + disableAgentCameraFollow(); + } + if (selectedAgentInspectorId === removedId) { + selectedAgentInspectorId = aiAgents[0]?.id || null; + if (selectedAgentInspectorId) renderAgentInspector(selectedAgentInspectorId); + else { + clearAgentInspectorViews(); + } + } + if (aiAgents.length === 0) { + disableAgentCameraFollow(); + } + renderAgentTaskUi(); +} + +function stopAiAgent(agent, reason = "manual-stop") { + if (!agent) return; + try { + if (agent.vlm) agent.vlm.enabled = false; + agent._plan = null; + agent._pendingDecision = null; + agent._setThought?.("Stopped"); + } catch {} + agentUiPush(`${new Date().toLocaleTimeString()}\nAGENT STOP\n${agent.id} (${reason})`); + renderAgentTaskUi(); +} + +function despawnEphemeralAgents(reason = "task-end") { + const doomed = aiAgents.filter((a) => a?._ephemeral === true); + for (const a of doomed) removeAiAgent(a, reason); +} + +function createAiAgent({ ephemeral = false } = {}) { + const endpoint = localStorage.getItem("sparkWorldVlmEndpoint") || "/vlm/decision"; + const model = resolveActiveVlmModel(); + const nearbyRange = 2.5; + const id = `agent-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`; + let agentRef = null; + const agent = new AiAvatar({ + id, + scene, + rapierWorld, + RAPIER, + getWorldKey: () => worldKey, + getTags: () => tags, + getPlayerPosition: () => (Array.isArray(window.__playerPosition) ? window.__playerPosition : [0, 0, 0]), + avatarUrl: ["/agent-model/unitree_go2.glb", "/agent-model/robot.glb"], + senseRadius: 3.0, + walkSpeed: 2.0, + // Headless mode in dimos: skip visual rendering, keep colliders for physics + headless: false, + vlm: { + // In dimos mode, VLM is disabled — agent pose is driven externally via /odom. + // Ephemeral workers auto-enable; manually spawned agents start idle. + enabled: dimosMode ? false : true, + showSpeechBubbleInScene: false, + holdPositionWhenIdle: true, + endpoint, + model, + getModel: resolveActiveVlmModel, + actions: ACTIVE_VLM_ACTIONS, + buildPrompt: () => buildActiveVlmPrompt(), + request: requestVlmDecision, + captureBase64: async (a) => { + // If a sensor mode is active (RGB-D, LiDAR, Compare), capture the + // on-screen view which already renders in that mode via renderActiveView. + if (simSensorViewMode !== "rgb" || simCompareView) { + const [ax, ay, az] = a.getPosition?.() || [0, 0, 0]; + const yaw = a.group?.rotation?.y ?? 0; + const pitch = typeof a.pitch === "number" ? a.pitch : 0; + const cp = Math.cos(pitch), sp = Math.sin(pitch); + const eyeY = ay + PLAYER_EYE_HEIGHT * 0.9; + const prevPos = camera.position.clone(); + const prevQuat = camera.quaternion.clone(); + camera.position.set(ax, eyeY, az); + camera.lookAt(ax + Math.sin(yaw) * cp, eyeY + sp, az + Math.cos(yaw) * cp); + camera.updateProjectionMatrix(); + camera.updateMatrixWorld(true); + renderActiveView(); + const dataUrl = renderer.domElement.toDataURL("image/jpeg", 0.8); + camera.position.copy(prevPos); + camera.quaternion.copy(prevQuat); + camera.updateProjectionMatrix(); + camera.updateMatrixWorld(true); + const idx = dataUrl.indexOf("base64,"); + return idx !== -1 ? dataUrl.slice(idx + 7) : null; + } + return captureAgentPovBase64({ + agent: a, + renderer, + scene, + mainCamera: camera, + width: 960, + height: 432, + eyeHeight: PLAYER_EYE_HEIGHT * 0.9, + fov: camera.fov, + near: camera.near, + far: camera.far, + headLamp: null, + jpegQuality: 0.8, + }); + }, + decideEverySteps: ACTIVE_VLM_DEFAULTS.decideEverySteps, + stepMeters: ACTIVE_VLM_DEFAULTS.stepMeters, + getTask: () => ({ ..._getAgentTask(id) }), + getNearbyAssets: (a) => getNearbyAssetsForAgent(a, nearbyRange), + getNearbyPrimitives: (a) => getNearbyPrimitivesForAgent(a, nearbyRange), + isEditorMode: () => false, + interactAsset: ({ agent: a, assetId, actionId }) => agentInteractAsset({ agent: a, assetId, actionId }), + pickUpAsset: ({ agent: a, assetId }) => agentPickUpAsset(a, assetId), + dropAsset: ({ agent: a }) => agentDropAsset(a), + getHeldAsset: (a) => getAgentHeldAsset(a), + onCapture: (base64) => { + const id = agentRef?.id || ""; + const s = getOrCreateAgentInspectorState(id); + s.shot = base64 || ""; + if (!selectedAgentInspectorId) selectedAgentInspectorId = id; + if (selectedAgentInspectorId === id) agentUiSetShot(base64); + }, + onRequest: ({ endpoint: ep, model: m, prompt, context, imageBase64, messages }) => { + const id = agentRef?.id || ""; + const req = { + endpoint: ep, + model: m, + prompt, + context, + imageBytes: imageBase64 ? Math.floor((imageBase64.length * 3) / 4) : null, + messages, + }; + const s = getOrCreateAgentInspectorState(id); + s.request = req; + if (!selectedAgentInspectorId) selectedAgentInspectorId = id; + if (selectedAgentInspectorId === id) renderAgentInspector(id); + agentUiPush(`${new Date().toLocaleTimeString()}\nREQUEST ${m}\n${ep}\nagent=${id}`); + }, + onResponse: ({ raw, parsed }) => { + const id = agentRef?.id || ""; + const resp = { raw, parsed }; + const s = getOrCreateAgentInspectorState(id); + s.response = resp; + if (!selectedAgentInspectorId) selectedAgentInspectorId = id; + if (selectedAgentInspectorId === id) agentUiSetResponse(resp); + const action = typeof parsed?.action === "string" ? parsed.action : ""; + agentUiPush(`${new Date().toLocaleTimeString()}\nRESPONSE\n${action}\nagent=${id}`); + }, + onActionApplied: ({ action, params, plan }) => { + const id = agentRef?.id || ""; + agentUiPush( + `${new Date().toLocaleTimeString()}\nAPPLY ${action}\nparams=${JSON.stringify(params || {})}\nplan=${JSON.stringify(plan || {})}\nagent=${id}` + ); + }, + onTaskFinished: ({ summary }) => { + const agent = agentRef; + const summaryText = String(summary || "").trim(); + agentUiPush(`${new Date().toLocaleTimeString()}\nTASK FINISH${agent ? ` [${agent.id}]` : ""}\n${summaryText}`); + + // End this specific agent's task + if (agent) { + const task = _agentTasks.get(agent.id); + if (task) { + task.active = false; + task.finishedAt = Date.now(); + task.finishedReason = "model"; + task.lastSummary = summaryText; + } + } + + // Auto-despawn if ephemeral or configured to despawn after task + const shouldDespawn = + agent && + (agent._ephemeral === true || agent._autoDespawnAfterTask === true); + if (shouldDespawn) { + _agentTasks.delete(agent.id); + removeAiAgent(agent, "task-complete"); + } + + // Check if any agents still have active tasks + const anyActive = [..._agentTasks.values()].some((t) => t.active); + if (!anyActive) { + agentTask.active = false; + agentTask.finishedAt = Date.now(); + agentTask.finishedReason = "model"; + agentTask.lastSummary = summaryText; + disableAgentCameraFollow(); + } + renderAgentTaskUi(); + }, + onError: (err) => { + const id = agentRef?.id || ""; + agentUiPush(`${new Date().toLocaleTimeString()}\nERROR\n${String(err?.message || err)}\nagent=${id}`); + }, + onDecision: (d) => { + const thought = typeof d?.thought === "string" ? d.thought : ""; + const action = typeof d?.action === "string" ? d.action : ""; + const id = agentRef?.id || ""; + agentUiPush(`${new Date().toLocaleTimeString()}\nDECISION\n${thought}\n${action}\nagent=${id}`); + }, + }, + }); + agentRef = agent; + agent._ephemeral = !!ephemeral; + // Manually spawned editor agents should clean themselves up after task completion. + agent._autoDespawnAfterTask = true; + // Only inherit the active task if this agent was spawned as part of a worker pool (ephemeral). + // Manually spawned agents start idle and wait for their own task assignment. + agent._taskStartedAt = ephemeral ? Number(agentTask.startedAt || 0) : 0; + getOrCreateAgentInspectorState(id); + if (!selectedAgentInspectorId) selectedAgentInspectorId = id; + renderSelectedAgentControls(); + return agent; +} + + +async function ensureRapierLoaded() { + if (RAPIER) return; + if (!_rapierInitPromise) { + _rapierInitPromise = _doRapierInit(); + } + return _rapierInitPromise; +} + +async function _doRapierInit() { + RAPIER = await import("@dimforge/rapier3d-compat"); + await RAPIER.init(); + rapierWorld = new RAPIER.World({ x: 0, y: -9.81, z: 0 }); + worldBody = rapierWorld.createRigidBody(RAPIER.RigidBodyDesc.fixed()); + + const radius = PLAYER_RADIUS; + const halfHeight = PLAYER_HALF_HEIGHT; + playerBody = rapierWorld.createRigidBody( + RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(0, 3, 0) + ); + playerCollider = rapierWorld.createCollider( + RAPIER.ColliderDesc.capsule(halfHeight, radius).setFriction(0.0).setSensor(ghostMode), + playerBody + ); + + characterController = rapierWorld.createCharacterController(0.02); + characterController.setSlideEnabled(true); + characterController.enableAutostep(0.55, 0.25, true); + characterController.enableSnapToGround(0.25); + characterController.setMaxSlopeClimbAngle(Math.PI / 3); + characterController.setMinSlopeSlideAngle(Math.PI / 2); +} + +async function spawnOrMoveAiAtAim({ createNew = false, silent = false, ephemeral = false } = {}) { + await ensureRapierLoaded(); + const hit = rapierRaycastFromCamera(500); + const placement = hit + ? { point: hit.point, normal: hit.normal || { x: 0, y: 1, z: 0 } } + : getPlacementAtCrosshair({ raycastDistance: 500, fallbackDistance: 3, surfaceOffset: 0.0 }); + if (!hit && !silent) { + setStatus("No collider hit; spawned AI using crosshair fallback placement."); + } + + let agent = createNew ? null : aiAgents[0] || null; + if (!agent) { + if (aiAgents.length >= MAX_AGENT_COUNT) { + if (!silent) setStatus(`Agent cap reached (${MAX_AGENT_COUNT}).`); + return; + } + agent = createAiAgent({ ephemeral }); + aiAgents.push(agent); + } else if (ephemeral) { + agent._ephemeral = true; + } + + const n = placement.normal + ? new THREE.Vector3(placement.normal.x, placement.normal.y, placement.normal.z).normalize() + : new THREE.Vector3(0, 1, 0); + const offset = Math.max(0.12, (agent.radius ?? PLAYER_RADIUS) + 0.06); + const p0 = placement.point; + const candA = { x: p0.x + n.x * offset, y: p0.y + n.y * offset, z: p0.z + n.z * offset }; + const candB = { x: p0.x - n.x * offset, y: p0.y - n.y * offset, z: p0.z - n.z * offset }; + + let chosen = null; + chosen = findNearbyFreeSpotForCollider(agent.collider, candA, 2.0, 0.12); + if (!chosen) chosen = findNearbyFreeSpotForCollider(agent.collider, candB, 2.0, 0.12); + if (!chosen) chosen = findNearbyFreeSpotForCollider(agent.collider, { x: p0.x, y: p0.y + offset, z: p0.z }, 2.5, 0.12); + // Fallback: use placement point directly (slightly above surface) rather than failing silently. + if (!chosen) { + chosen = { x: p0.x + n.x * 0.5, y: p0.y + n.y * 0.5 + 0.5, z: p0.z + n.z * 0.5 }; + console.warn("[Spawn] No free collision spot found – using direct fallback placement at", chosen); + } + + agent.setPosition(chosen.x, chosen.y, chosen.z); + if (agentTask.active) { + agent._taskStartedAt = agentTask.startedAt; + } + renderAgentTaskUi(); + if (!silent) { + const label = createNew ? "AI worker spawned." : "AI placed."; + setStatus(`${label} (${aiAgents.length} total)`); + } +} + +function pickAgentFromRay(raycaster) { + const agentRoots = aiAgents.map((a) => a?.group).filter(Boolean); + if (agentRoots.length === 0) return null; + + // First try exact mesh hits. + const agentHits = raycaster.intersectObjects(agentRoots, true); + if (agentHits.length > 0) { + let obj = agentHits[0].object; + while (obj && !(typeof obj.name === "string" && obj.name.startsWith("AiAvatar:"))) obj = obj.parent; + const agentId = obj?.name?.slice("AiAvatar:".length) || ""; + const agent = aiAgents.find((a) => a.id === agentId) || null; + if (agent) return agent; + } + + // Fallback: broad proximity test against agent centers. + let best = null; + let bestT = Infinity; + const origin = raycaster.ray.origin; + const dir = raycaster.ray.direction; + const tmp = new THREE.Vector3(); + const to = new THREE.Vector3(); + const pickRadius = 0.45; // generous click radius for tiny capsules + + for (const a of aiAgents) { + if (!a?.group || a.group.visible === false) continue; + const [x, y, z] = a.getPosition?.() || [0, 0, 0]; + // Aim around torso center for better clickability. + tmp.set(x, y + (a.halfHeight || 0.25), z); + const d2 = raycaster.ray.distanceSqToPoint(tmp); + if (d2 > pickRadius * pickRadius) continue; + + // Prefer nearest along-ray candidate in front of camera. + to.copy(tmp).sub(origin); + const t = to.dot(dir); + if (t <= 0) continue; + if (t < bestT) { + bestT = t; + best = a; + } + } + return best; +} + +function pickAgentFromScreenPoint(clientX, clientY, canvasRect) { + if (!Number.isFinite(clientX) || !Number.isFinite(clientY) || !canvasRect || aiAgents.length === 0) return null; + const thresholdPx = 46; + let best = null; + let bestD2 = thresholdPx * thresholdPx; + const v = new THREE.Vector3(); + for (const a of aiAgents) { + if (!a?.group || a.group.visible === false) continue; + const [x, y, z] = a.getPosition?.() || [0, 0, 0]; + v.set(x, y + (a.halfHeight || 0.25), z).project(camera); + if (v.z < -1 || v.z > 1) continue; // behind camera / clipped + const sx = canvasRect.left + (v.x * 0.5 + 0.5) * canvasRect.width; + const sy = canvasRect.top + (-v.y * 0.5 + 0.5) * canvasRect.height; + const dx = sx - clientX; + const dy = sy - clientY; + const d2 = dx * dx + dy * dy; + if (d2 < bestD2) { + bestD2 = d2; + best = a; + } + } + return best; +} + +function ensureAgentBadgeLayer() { + if (agentBadgeLayerEl) return; + installAgentBadgeEventDelegation(); + const el = document.createElement("div"); + el.id = "agent-badge-layer"; + el.className = "agent-badge-layer"; + document.body.appendChild(el); + agentBadgeLayerEl = el; +} + +function getOrCreateAgentBadge(agentId) { + const id = String(agentId || ""); + if (!id) return null; + ensureAgentBadgeLayer(); + if (agentBadgeElsById.has(id)) return agentBadgeElsById.get(id); + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "agent-badge"; + btn.textContent = id; + btn.dataset.agentId = id; + btn.addEventListener("pointerdown", (e) => { + e.preventDefault(); + e.stopPropagation(); + selectAgentInspector(id); + setStatus(`Inspecting ${id}. Use right panel controls.`); + }); + document.body.appendChild(btn); + agentBadgeElsById.set(id, btn); + return btn; +} + +function installAgentBadgeEventDelegation() { + if (typeof document === "undefined") return; + if (document.body?.dataset?.agentBadgeDelegationInstalled === "1") return; + if (document.body) document.body.dataset.agentBadgeDelegationInstalled = "1"; + // Capture-phase delegation to beat canvas/overlay handlers. + document.addEventListener( + "pointerdown", + (e) => { + const target = e.target; + if (!(target instanceof HTMLElement)) return; + const badge = target.closest(".agent-badge"); + if (!badge) return; + const id = String(badge.dataset.agentId || "").trim(); + if (!id) return; + e.preventDefault(); + e.stopPropagation(); + selectAgentInspector(id); + setStatus(`Inspecting ${id}. Use right panel controls.`); + }, + true + ); +} + +function removeAgentBadge(agentId) { + const id = String(agentId || ""); + const el = agentBadgeElsById.get(id); + if (el?.parentElement) el.parentElement.removeChild(el); + agentBadgeElsById.delete(id); +} + + +function pickTagMarkerFromCamera() { + _raycaster.setFromCamera({ x: 0, y: 0 }, camera); + const hits = _raycaster.intersectObjects(tagsGroup.children, false); + for (const h of hits) { + const obj = h.object; + if (obj?.userData?.isRadius) continue; + const id = obj?.userData?.tagId; + if (id) return id; + } + return null; +} + + + +// Lock pointer and interact on click for FPS navigation. +canvas.addEventListener("click", async (e) => { + if (!controls.isLocked) { + controls.enabled = true; + try { controls.lock(); } catch {} + } else if (e.button === 0) { + await handlePlayerInteraction(); + } +}); + +// Right-click to lock pointer (for FPS navigation) +canvas.addEventListener("contextmenu", (e) => { + e.preventDefault(); + if (!controls.isLocked) { + controls.enabled = true; + try { controls.lock(); } catch {} + } +}); + +controls.addEventListener("lock", () => { + controls.enabled = true; +}); +controls.addEventListener("unlock", () => { + setStatus("Click to look around."); +}); + +resetBtn?.addEventListener("click", () => { + controls.object.position.set(0, 1.7, 4); +}); + + +function setGhostMode(enabled) { + ghostMode = !!enabled; + // Ghost mode indicator shown in status + if (enabled) setStatus("Ghost mode ON"); + + // Disable collisions by turning the player collider into a sensor. + // (Sensors don't generate contact forces, so you can pass through walls.) + try { + if (playerCollider && typeof playerCollider.setSensor === "function") { + playerCollider.setSensor(ghostMode); + } + } catch { + // ignore + } +} + +// Ghost mode toggled via 'G' key only + +// Tagging UI +document.documentElement.dataset.mode = "sim"; +simPanelCollapseBtn?.addEventListener("click", () => { + simPanelCollapsed = true; + applySimPanelCollapsedState(); +}); +simPanelOpenBtn?.addEventListener("click", () => { + simPanelCollapsed = false; + applySimPanelCollapsedState(); +}); +simCameraModeToggleBtn?.addEventListener("click", () => { + simUserCameraMode = simUserCameraMode === "user" ? "agent" : "user"; + localStorage.setItem("sparkWorldSimCameraMode", simUserCameraMode); + updateSimCameraModeToggleUi(); + if (simUserCameraMode === "user") { + if (agentCameraFollow) disableAgentCameraFollow(); + } else if (agentTask.active) { + enableAgentCameraFollow(); + } +}); +simViewRgbdBtn?.addEventListener("click", () => { + simCompareView = false; + setSimSensorViewMode("rgbd"); +}); +simRgbdGrayBtn?.addEventListener("click", () => { + rgbdVizMode = "gray"; + updateSimSensorButtons(); + if (simSensorViewMode === "rgbd") setStatus("RGB-D: metric grayscale"); +}); +simRgbdColormapBtn?.addEventListener("click", () => { + rgbdVizMode = "colormap"; + updateSimSensorButtons(); + if (simSensorViewMode === "rgbd") setStatus("RGB-D: metric colormap"); +}); +simRgbdAutoRangeBtn?.addEventListener("click", () => { + rgbdAutoRange = !rgbdAutoRange; + updateSimSensorButtons(); + if (simSensorViewMode === "rgbd") setStatus(rgbdAutoRange ? "RGB-D auto-range ON (p5/p95)" : "RGB-D auto-range OFF"); +}); +simRgbdNoiseBtn?.addEventListener("click", () => { + rgbdNoiseEnabled = !rgbdNoiseEnabled; + updateSimSensorButtons(); + setStatus(rgbdNoiseEnabled ? "RGB-D noise ON" : "RGB-D noise OFF"); +}); +simRgbdSpeckleBtn?.addEventListener("click", () => { + rgbdSpeckleEnabled = !rgbdSpeckleEnabled; + updateSimSensorButtons(); + setStatus(rgbdSpeckleEnabled ? "RGB-D speckle ON" : "RGB-D speckle OFF"); +}); +simRgbdMinEl?.addEventListener("input", () => { + if (rgbdAutoRange) return; + const minV = Number(simRgbdMinEl.value); + const maxV = Number(simRgbdMaxEl?.value ?? rgbdRangeMaxM); + setRgbdRange(minV, maxV); +}); +simRgbdMaxEl?.addEventListener("input", () => { + if (rgbdAutoRange) return; + const minV = Number(simRgbdMinEl?.value ?? rgbdRangeMinM); + const maxV = Number(simRgbdMaxEl.value); + setRgbdRange(minV, maxV); +}); +simRgbdPcOverlayBtn?.addEventListener("click", () => { + rgbdPcOverlayOnLidar = !rgbdPcOverlayOnLidar; + _rgbdPcOverlayLastUpdateMs = 0; + _rgbdPcOverlayLastPose = null; + _rgbdPcOverlayDirty = rgbdPcOverlayOnLidar; + if (!rgbdPcOverlayOnLidar) { + _rgbdPcGeom.setDrawRange(0, 0); + _rgbdPcOverlayLastCount = 0; + _rgbdPcOverlayDirty = false; + } + if (rgbdPcOverlayOnLidar) { + // Overlay button should directly enter combined LiDAR+RGBD-PC debug mode. + simCompareView = false; + lidarOrderedDebugView = false; + if (simSensorViewMode !== "lidar") simSensorViewMode = "lidar"; + applySimSensorViewMode(); + } + // Actual visibility is finalized by updateRgbdPcOverlayCloud once points are generated. + rgbdPcOverlayGroup.visible = false; + updateSimSensorButtons(); + setStatus(rgbdPcOverlayOnLidar ? `RGB-D->PointCloud overlay ON (${_rgbdPcOverlayLastCount} pts)` : "RGB-D->PointCloud overlay OFF"); +}); +simViewLidarBtn?.addEventListener("click", () => { + // Main LiDAR button always maps to accumulated unordered 3D point cloud. + simCompareView = false; + lidarOrderedDebugView = false; + if (simSensorViewMode !== "lidar") { + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + } + setSimSensorViewMode("lidar"); + if (rgbdPcOverlayOnLidar) _rgbdPcOverlayDirty = true; +}); +simViewCompareBtn?.addEventListener("click", () => { + simCompareView = !simCompareView; + if (simCompareView) { + // Auto-collapse panel so tiles get full canvas width. + simPanelCollapsed = true; + applySimPanelCollapsedState(); + simSensorViewMode = "lidar"; + lidarOrderedDebugView = false; + if (rgbdPcOverlayOnLidar) _rgbdPcOverlayDirty = true; + setStatus("Compare view: RGB | RGB-D | LiDAR"); + } else { + simPanelCollapsed = false; + applySimPanelCollapsedState(); + setStatus("Compare view OFF"); + } + applySimSensorViewMode(); +}); +simLidarColorRangeBtn?.addEventListener("click", () => { + lidarColorByRange = !lidarColorByRange; + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") { + updateLidarPointCloud(); + setStatus(lidarColorByRange ? "LiDAR: range-color mode" : "LiDAR: intensity mode"); + } +}); +simLidarOrderedDebugBtn?.addEventListener("click", () => { + // Single Sweep is the explicit ring/scan debug view. + lidarOrderedDebugView = true; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + if (simSensorViewMode !== "lidar") simSensorViewMode = "lidar"; + updateSimSensorButtons(); + applySimSensorViewMode(); + setStatus("LiDAR: single sweep view"); +}); +simLidarNoiseBtn?.addEventListener("click", () => { + lidarNoiseEnabled = !lidarNoiseEnabled; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + setStatus(lidarNoiseEnabled ? "LiDAR noise ON" : "LiDAR noise OFF"); +}); +simLidarMultiReturnBtn?.addEventListener("click", () => { + lidarMultiReturnMode = lidarMultiReturnMode === "strongest" ? "last" : "strongest"; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + setStatus(`LiDAR return mode: ${lidarMultiReturnMode}`); +}); +spawnAiBtn?.addEventListener("click", async () => { + try { + await spawnOrMoveAiAtAim({ createNew: false, ephemeral: false }); + } catch (err) { + console.error("[Spawn] Error spawning agent:", err); + setStatus("Spawn failed: " + (err?.message || String(err))); + } +}); + +// --- Blob shadow live-adjustment helpers --- +// Updates the blob shadow mesh in-place without rebuilding the entire asset. +function updateBlobShadowLive(assetId) { + const a = assets.find((x) => x.id === assetId); + if (!a?.castShadow) return; + const root = assetsGroup.getObjectByName(`asset:${assetId}`); + if (!root) return; + const blob = root.getObjectByName(`blobShadow:${assetId}`); + if (!blob) return; + const bs = a.blobShadow || {}; + // Opacity + if (blob.material) blob.material.opacity = bs.opacity ?? 0.5; + // Scale + stretch + const baseDiam = blob.userData._baseDiameter || 1; + const userScale = bs.scale ?? 1.0; + const stretch = bs.stretch ?? 1.0; + const d = baseDiam * userScale; + blob.scale.set(d * stretch, 1, d / stretch); + // Rotation (Y axis, degrees → radians) + blob.rotation.y = ((bs.rotationDeg ?? 0) * Math.PI) / 180; + // Offset + blob.position.x = bs.offsetX ?? 0; + const baseY = blob.userData._baseLocalY ?? blob.position.y; + blob.position.y = baseY + (bs.offsetY ?? 0); + blob.position.z = bs.offsetZ ?? 0; +} + + +// Initialize agent UI visibility/content. +applySimPanelCollapsedState(); +renderAgentTaskUi(); +agentTaskStartBtn?.addEventListener("click", () => { + if (agentTask.active) return; + void startAgentTask(agentTaskInputEl?.value); +}); +agentTaskEndBtn?.addEventListener("click", () => endAgentTask("manual")); +// Enter key in command input starts task; stop propagation so WASD doesn't trigger +agentTaskInputEl?.addEventListener("keydown", (e) => { + e.stopPropagation(); + if (e.key === "Enter" && !agentTask.active && aiAgents.length > 0) { + void startAgentTask(agentTaskInputEl.value); + } +}); + +// Shared import logic — used by both editor Import and sim Load Level +async function importLevelFromJSON(json, options = {}) { + const importedTags = Array.isArray(json?.tags) ? json.tags : Array.isArray(json) ? json : null; + const preserveAssetsWhenMissing = options.preserveAssetsWhenMissing === true; + const importedAssets = Array.isArray(json?.assets) + ? json.assets + : (preserveAssetsWhenMissing ? assets : []); + const importedPrimitives = Array.isArray(json?.primitives) ? json.primitives : []; + const importedLights = Array.isArray(json?.lights) ? json.lights : []; + const importedSceneSettings = json && typeof json === "object" && json.sceneSettings + ? normalizeSceneSettings(json.sceneSettings) + : null; + if (!importedTags) throw new Error("Invalid level file."); + // Clean up old primitive colliders + for (const p of primitives) removePrimitiveCollider(p); + tags = importedTags; + assets = importedAssets; + primitives = importedPrimitives; + editorLights = importedLights; + if (importedSceneSettings) sceneSettings = importedSceneSettings; + if (!options.skipWorldSave) saveTagsForWorld(); + rebuildTagMarkers(); + await rebuildAssets(); + rebuildAllPrimitives(); + rebuildAllEditorLights(); + renderTagsList(); + renderAssetsList(); + renderPrimitivesList(); + renderTagPanel(); + applySceneSkySettings(); + applySceneRgbBackground(); + syncShadowMapEnabled(); +} + + +// Sim-mode "Load Level JSON" input (only exists in sim.html) +const simLevelImportEl = document.getElementById("sim-level-import"); +simLevelImportEl?.addEventListener("change", async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + try { + setStatus("Loading level..."); + const text = await file.text(); + await importLevelFromJSON(JSON.parse(text)); + await ensureRapierLoaded(); + spawnPlayerInsideScene(); + setStatus("Level loaded. Click to enter, then spawn an agent."); + } catch (err) { + console.error(err); + setStatus(err?.message || "Failed to load level."); + } finally { + e.target.value = ""; + } +}); + +canvas?.addEventListener("mousedown", () => { + const id = pickTagMarkerFromCamera(); + if (!id) return; + selectedTagId = id; + draftTag = null; + updateMarkerMaterials(); + renderTagsList(); + renderTagPanel(); +}); + + +// ============================================================================= +// PRIMITIVE & LIGHT EVENT HANDLERS +// ============================================================================= + + +// Expose tag data for "simulation mode" consumers. +globalThis.sparkWorld = globalThis.sparkWorld || {}; +globalThis.sparkWorld.getWorldKey = () => worldKey; +globalThis.sparkWorld.getTags = () => tags.slice(); +globalThis.sparkWorld.getAiAgents = () => aiAgents.map((a) => ({ id: a.id, position: a.getPosition?.() })); + +function teleportPlayerTo(x, y, z) { + if (!playerBody) return; + playerBody.setTranslation({ x, y, z }, true); + playerBody.setLinvel({ x: 0, y: 0, z: 0 }, true); +} + +// Find a reasonable interior floor Y by casting a ray straight down through the +// loaded scene meshes and picking the lowest up-facing surface. This skips the +// roof when a building has both a roof and an interior floor, so the player +// lands inside rather than on top. +function _findSceneFloorY(x = 0, z = 0) { + const bbox = new THREE.Box3(); + const tmp = new THREE.Box3(); + try { tmp.setFromObject(assetsGroup); if (tmp.isEmpty() === false) bbox.union(tmp); } catch {} + try { tmp.setFromObject(primitivesGroup); if (tmp.isEmpty() === false) bbox.union(tmp); } catch {} + const fromY = bbox.isEmpty() ? 50 : bbox.max.y + 5; + const raycaster = new THREE.Raycaster( + new THREE.Vector3(x, fromY, z), + new THREE.Vector3(0, -1, 0), + 0, + fromY + 500, + ); + const hits = [ + ...raycaster.intersectObject(assetsGroup, true), + ...raycaster.intersectObject(primitivesGroup, true), + ]; + let floorY = null; + for (const h of hits) { + const n = h.face?.normal; + if (!n) continue; + // Transform face normal to world space to test "up-facing" + const worldN = n.clone().transformDirection(h.object.matrixWorld); + if (worldN.y < 0.5) continue; // skip walls/ceilings + if (floorY == null || h.point.y < floorY) floorY = h.point.y; + } + return floorY; +} + +function spawnPlayerInsideScene() { + const floorY = _findSceneFloorY(0, 0); + if (floorY == null) return false; + const y = floorY + PLAYER_EYE_HEIGHT; + camera.position.set(0, y, 0); + teleportPlayerTo(0, y, 0); + return true; +} + + +function safeDisableGhost() { + // If we're currently inside occupied geometry, turning collisions back on will + // trap the character (penetration state). Use Rapier query pipeline to find a safe spot. + if (!playerBody) return setGhostMode(false); + const p = playerBody.translation(); + + // Use Rapier query pipeline to find a non-penetrating spot. + if (rapierWorld && playerCollider) { + try { + const shape = playerCollider.shape; + const rot = playerCollider.rotation(); + const here = { x: p.x, y: p.y, z: p.z }; + + const intersectsHere = rapierWorld.queryPipeline.intersectionWithShape( + rapierWorld.bodies, + rapierWorld.colliders, + here, + rot, + shape, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + playerCollider.handle + ); + + // If we're not intersecting anything solid, we can safely disable ghost immediately. + if (intersectsHere == null) { + setGhostMode(false); + setStatus("Ghost disabled."); + return; + } + + const tryOffsets = (maxR, step) => { + for (let r = step; r <= maxR; r += step) { + // sample a handful of directions per radius + const dirs = [ + [1, 0, 0], + [-1, 0, 0], + [0, 0, 1], + [0, 0, -1], + [1, 0, 1], + [1, 0, -1], + [-1, 0, 1], + [-1, 0, -1], + [0, 1, 0], + [0, -1, 0], + ]; + for (const [dx, dy, dz] of dirs) { + const len = Math.hypot(dx, dy, dz) || 1; + const pos = { x: p.x + (dx / len) * r, y: p.y + (dy / len) * r, z: p.z + (dz / len) * r }; + const hit = rapierWorld.queryPipeline.intersectionWithShape( + rapierWorld.bodies, + rapierWorld.colliders, + pos, + rot, + shape, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, + undefined, + playerCollider.handle + ); + if (hit == null) return pos; + } + } + return null; + }; + + const pos = tryOffsets(2.5, 0.15); + if (pos) { + teleportPlayerTo(pos.x, pos.y, pos.z); + setGhostMode(false); + setStatus("Ghost disabled (moved to nearest free space)."); + return; + } + } catch { + // ignore + } + } + + setStatus("Couldn't find free space to disable Ghost. Staying in Ghost mode."); + setGhostMode(true); +} + +window.addEventListener("keydown", (e) => { + const tagName = e.target?.tagName?.toLowerCase?.(); + const isTyping = + tagName === "input" || tagName === "textarea" || tagName === "select" || e.target?.isContentEditable; + if (!isTyping) { + if (e.code === "KeyB") { + void spawnOrMoveAiAtAim({ createNew: false, ephemeral: false }); + e.preventDefault(); + } + } + if (e.code === "KeyW") keys.forward = true; + if (e.code === "KeyS") keys.backward = true; + if (e.code === "KeyA") keys.left = true; + if (e.code === "KeyD") keys.right = true; + if (e.code === "Space") keys.up = true; + if (e.code === "ShiftLeft" || e.code === "ShiftRight") keys.down = true; + if (e.code === "KeyF") flyMode = !flyMode; + if (e.code === "KeyG") { + if (ghostMode) safeDisableGhost(); + else setGhostMode(true); + } + + // === PLAYER INTERACTION KEYS === + // E key to interact with asset at crosshair + if (e.code === "KeyE" && controls?.isLocked && !isTyping) { + handlePlayerInteraction(); + e.preventDefault(); + } + if (e.code === "KeyR" && controls?.isLocked && !isTyping && !isInteractionPopupVisible()) { + if (cycleInteractableTarget(1)) { + updatePlayerInteractionHint(); + e.preventDefault(); + } + } + + // Escape to close interaction popup + if (e.code === "Escape" && isInteractionPopupVisible()) { + hideInteractionPopup(); + // Re-lock pointer after closing popup + controls?.lock?.(); + e.preventDefault(); + } + + // Number keys 1-9 to select action when popup is visible + if (isInteractionPopupVisible() && _currentInteractableAsset) { + const numMatch = e.code.match(/^(?:Digit|Numpad)([1-9])$/); + if (numMatch) { + const idx = parseInt(numMatch[1], 10) - 1; + const { asset, actions } = _currentInteractableAsset; + if (idx >= 0 && idx < actions.length) { + const actionId = actions[idx].id; + + // Hide popup and re-lock pointer FIRST (before async operations) + hideInteractionPopup(); + + // Execute the action + if (actionId === "__PICK_UP__") { + playerPickUpAsset(asset.id); + } else { + executePlayerInteraction(asset.id, actionId); + } + + // Re-lock pointer (use setTimeout since pointer lock may need a moment) + setTimeout(() => { + try { + controls?.lock?.(); + } catch (err) { + // Pointer lock requires user gesture, may fail silently + } + }, 10); + + e.preventDefault(); + e.stopPropagation(); + return; + } + } + } +}); + +window.addEventListener("keyup", (e) => { + if (e.code === "KeyW") keys.forward = false; + if (e.code === "KeyS") keys.backward = false; + if (e.code === "KeyA") keys.left = false; + if (e.code === "KeyD") keys.right = false; + if (e.code === "Space") keys.up = false; + if (e.code === "ShiftLeft" || e.code === "ShiftRight") keys.down = false; +}); + + +// Shadow catcher: a large transparent ground plane that only shows shadows. +// ShadowMaterial is fully transparent where there's no shadow, so the splat +// floor shows through, but shadows appear as dark patches on top. +const shadowCatcherMat = new THREE.ShadowMaterial({ opacity: 0.35 }); +const shadowCatcher = new THREE.Mesh( + new THREE.PlaneGeometry(200, 200), + shadowCatcherMat +); +shadowCatcher.rotation.x = -Math.PI / 2; // lie flat +shadowCatcher.position.y = 0.001; // just above grid to avoid z-fighting +shadowCatcher.receiveShadow = true; +shadowCatcher.name = "__shadowCatcher"; +scene.add(shadowCatcher); +// Add to scene lights registry so it's controllable from the editor +sceneLights.push({ id: "_shadow_ground", label: "Shadow Ground", obj: shadowCatcher, type: "shadow_ground" }); + +function _hasBumpableAssets() { + for (const a of assets) { if (a?.bumpable) return true; } + return false; +} + +function updateBumpableAssets(dt, playerPos, agentPushers = []) { + if (!playerPos || !_hasBumpableAssets()) { + _playerPosPrevForBumpValid = false; + return; + } + if (!_playerPosPrevForBumpValid) { + _playerPosPrevForBump.copy(playerPos); + _playerPosPrevForBumpValid = true; + return; + } + const playerVel = new THREE.Vector3().subVectors(playerPos, _playerPosPrevForBump).divideScalar(Math.max(dt, 1e-3)); + _playerPosPrevForBump.copy(playerPos); + const speedXZ = Math.hypot(playerVel.x, playerVel.z); + const playerCanPush = !ghostMode; + const intent = new THREE.Vector3(); + const camForward = new THREE.Vector3(); + camera.getWorldDirection(camForward); + camForward.y = 0; + if (camForward.lengthSq() > 1e-6) camForward.normalize(); + const camRight = new THREE.Vector3().crossVectors(camForward, camera.up).normalize(); + if (keys.forward) intent.add(camForward); + if (keys.backward) intent.sub(camForward); + if (keys.right) intent.add(camRight); + if (keys.left) intent.sub(camRight); + if (intent.lengthSq() > 1e-6) intent.normalize(); + const intentPush = playerCanPush && intent.lengthSq() > 0; + const pushDir = intentPush ? intent.clone() : new THREE.Vector3(playerVel.x, 0, playerVel.z); + if (pushDir.lengthSq() > 1e-6) pushDir.normalize(); + const playerRadius = 0.35; + const pushThreshold = 0.05; + let anyMoved = false; + let anyColliderNeedsSync = false; + for (const a of assets) { + if (!a?.bumpable) continue; + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (!obj) continue; + const vel = _assetBumpVelocities.get(a.id) || new THREE.Vector3(); + const localCenter = obj.userData?._localSphereCenter || new THREE.Vector3(); + const worldCenter = localCenter.clone(); + obj.localToWorld(worldCenter); + const worldRadius = (obj.userData?._localSphereRadius || 0.6) * Math.max(obj.scale.x, obj.scale.y, obj.scale.z); + const dx = worldCenter.x - playerPos.x; + const dz = worldCenter.z - playerPos.z; + const dist = Math.hypot(dx, dz); + const minDist = worldRadius + playerRadius; + const ahead = pushDir.lengthSq() > 0 ? (dx * pushDir.x + dz * pushDir.z) : 0; + const lateral = pushDir.lengthSq() > 0 ? Math.abs(dx * -pushDir.z + dz * pushDir.x) : dist; + const inPushCone = intentPush && ahead > -0.05 && ahead < (minDist + 0.9) && lateral < (worldRadius + 0.55); + if (playerCanPush && (dist < (minDist + 0.35) || inPushCone) && (speedXZ > pushThreshold || intentPush)) { + const dirX = dist > 1e-3 ? dx / dist : (intentPush ? pushDir.x : (Math.sign(playerVel.x) || 1)); + const dirZ = dist > 1e-3 ? dz / dist : (intentPush ? pushDir.z : (Math.sign(playerVel.z) || 0)); + const penetration = minDist - dist; + const response = Number(a.bumpResponse) || 0.9; + const driveSpeed = Math.max(speedXZ, intentPush ? 1.4 : 0); + const intentBonus = inPushCone ? 0.35 : 0; + const impulse = Math.min(2.4, (Math.max(0, penetration) * 3 + driveSpeed * 0.35 + intentBonus) * response); + vel.x += dirX * impulse; + vel.z += dirZ * impulse; + } + // AI agents can push bumpable assets as well. + for (const ap of agentPushers) { + const apPos = ap?.pos; + const apVel = ap?.vel; + if (!apPos || !apVel) continue; + const av = Math.hypot(apVel.x || 0, apVel.z || 0); + if (av <= 0.04) continue; + const adx = worldCenter.x - apPos.x; + const adz = worldCenter.z - apPos.z; + const adist = Math.hypot(adx, adz); + const aminDist = worldRadius + Math.max(0.22, Number(ap.radius) || 0.22); + if (adist > aminDist + 0.3) continue; + const dirX = adist > 1e-3 ? adx / adist : (Math.sign(apVel.x) || 1); + const dirZ = adist > 1e-3 ? adz / adist : (Math.sign(apVel.z) || 0); + const penetration = aminDist - adist; + const response = Number(a.bumpResponse) || 0.9; + const impulse = Math.min(2.2, (Math.max(0, penetration) * 2.4 + av * 0.28) * response); + vel.x += dirX * impulse; + vel.z += dirZ * impulse; + } + const damping = Math.min(0.995, Math.max(0.65, Number(a.bumpDamping) || 0.9)); + const dampPow = Math.pow(damping, dt * 60); + vel.multiplyScalar(dampPow); + const maxSpeed = 2.5; + const speed = Math.hypot(vel.x, vel.z); + if (speed > maxSpeed) { + const s = maxSpeed / speed; + vel.x *= s; + vel.z *= s; + } + if (vel.lengthSq() < 1e-4) { + vel.set(0, 0, 0); + _assetBumpVelocities.set(a.id, vel); + continue; + } + let moveX = THREE.MathUtils.clamp(vel.x * dt, -0.2, 0.2); + let moveZ = THREE.MathUtils.clamp(vel.z * dt, -0.2, 0.2); + const myBox = new THREE.Box3().setFromObject(obj); + const testBoxX = myBox.clone().translate(new THREE.Vector3(moveX, 0, 0)); + const testBoxZ = myBox.clone().translate(new THREE.Vector3(0, 0, moveZ)); + let blockedX = false, blockedZ = false; + const checkCollision = (testBox, excludeObj) => { + for (const child of primitivesGroup.children) { + if (child === excludeObj) continue; + const cb = new THREE.Box3().setFromObject(child); + if (!cb.isEmpty() && testBox.intersectsBox(cb)) return true; + } + for (const child of assetsGroup.children) { + if (child === excludeObj) continue; + if (child.userData?.isBlobShadow) continue; + const cb = new THREE.Box3().setFromObject(child); + if (!cb.isEmpty() && testBox.intersectsBox(cb)) return true; + } + return false; + }; + if (Math.abs(moveX) > 1e-5 && checkCollision(testBoxX, obj)) { + blockedX = true; + vel.x *= -0.15; + } + if (Math.abs(moveZ) > 1e-5 && checkCollision(testBoxZ, obj)) { + blockedZ = true; + vel.z *= -0.15; + } + if (!blockedX) obj.position.x += moveX; + if (!blockedZ) obj.position.z += moveZ; + if (blockedX && blockedZ) { + _assetBumpVelocities.set(a.id, vel); + continue; + } + anyMoved = true; + anyColliderNeedsSync = true; + if (!a.transform) a.transform = {}; + if (!a.transform.position) a.transform.position = { x: 0, y: 0, z: 0 }; + a.transform.position.x = obj.position.x; + a.transform.position.z = obj.position.z; + _assetBumpVelocities.set(a.id, vel); + } + if (anyMoved) { + const now = performance.now(); + if (now - _lastBumpSaveAt > 500) { + _lastBumpSaveAt = now; + saveTagsForWorld(); + } + if (anyColliderNeedsSync && now - _lastBumpColliderSyncAt > 50) { + _lastBumpColliderSyncAt = now; + for (const a of assets) { + if (!a?.bumpable) continue; + if (!_assetBumpVelocities.has(a.id)) continue; + const v = _assetBumpVelocities.get(a.id); + if (!v || v.lengthSq() < 1e-4) continue; + rebuildAssetCollider(a.id); + } + } + } +} + +function collectAgentBumpPushers(dt) { + const pushers = []; + const alive = new Set(); + const invDt = 1 / Math.max(dt, 1e-3); + for (const agent of aiAgents) { + const id = String(agent?.id || ""); + const posRaw = agent?.body?.translation?.(); + if (!id || !posRaw) continue; + alive.add(id); + const pos = new THREE.Vector3(posRaw.x, posRaw.y, posRaw.z); + const prev = _agentPosPrevForBump.get(id); + const vel = prev ? pos.clone().sub(prev).multiplyScalar(invDt) : new THREE.Vector3(); + _agentPosPrevForBump.set(id, pos.clone()); + pushers.push({ + id, + pos, + vel, + radius: Math.max(0.2, Number(agent?.radius) || 0.2), + }); + } + for (const id of _agentPosPrevForBump.keys()) { + if (!alive.has(id)) _agentPosPrevForBump.delete(id); + } + return pushers; +} + +function updateRapier(dt) { + // No physics world loaded → free-fly camera movement so user can still navigate + if (!rapierWorld || !playerBody) { + const flySpeed = 8.0; + const fwd = new THREE.Vector3(); + camera.getWorldDirection(fwd); + const right = new THREE.Vector3().crossVectors(fwd, camera.up).normalize(); + const move = new THREE.Vector3(); + if (keys.forward) move.add(fwd); + if (keys.backward) move.sub(fwd); + if (keys.right) move.add(right); + if (keys.left) move.sub(right); + if (keys.up) move.y += 1; + if (keys.down) move.y -= 1; + if (move.lengthSq() > 0) { + move.normalize().multiplyScalar(flySpeed * dt); + controls.object.position.add(move); + avatar.position.copy(controls.object.position).y -= PLAYER_EYE_HEIGHT; + } + return; + } + + // Flush any deferred collider builds BEFORE stepping + flushPendingColliderBuilds(); + + // Step physics FIRST — this integrates last frame's kinematic moves and + // updates the query pipeline internally, avoiding the RefCell double-borrow + // that happens with manual `queryPipeline.update(colliders)`. + rapierWorld.timestep = dt; + try { + rapierWorld.step(); + _rapierStepFaultCount = 0; + } catch (e) { + _rapierStepFaultCount += 1; + console.warn(`[RAPIER] step() failed (${_rapierStepFaultCount})`, e); + // Prevent hard crash loop; skip this frame and try again next tick. + return; + } + + // Sync camera and avatar to the body position that step() just resolved + const p = playerBody.translation(); + + // Skip player movement when camera is following agent + if (agentCameraFollow) { + avatar.position.set(p.x, p.y, p.z); + return; + } + + const baseSpeed = 6.0; + const runSpeed = 10.0; + const flySpeed = 8.0; + const speed = flyMode ? flySpeed : keys.down ? runSpeed : baseSpeed; + const gravity = 20.0; + const jumpVel = 8.0; + + const forward = new THREE.Vector3(); + camera.getWorldDirection(forward); + forward.y = 0; + forward.normalize(); + const right = new THREE.Vector3().crossVectors(forward, camera.up).normalize(); + + const wish = new THREE.Vector3(); + if (keys.forward) wish.add(forward); + if (keys.backward) wish.sub(forward); + if (keys.right) wish.add(right); + if (keys.left) wish.sub(right); + if (wish.lengthSq() > 0) wish.normalize(); + + const upDown = flyMode ? (keys.up ? 1 : 0) + (keys.down ? -1 : 0) : 0; + + const t = p; // body position after step + let desired = { x: 0, y: 0, z: 0 }; + + if (ghostMode) { + desired = { + x: wish.x * flySpeed * dt, + y: ((keys.up ? 1 : 0) + (keys.down ? -1 : 0)) * flySpeed * dt, + z: wish.z * flySpeed * dt, + }; + playerBody.setNextKinematicTranslation({ + x: t.x + desired.x, + y: t.y + desired.y, + z: t.z + desired.z, + }); + } else if (flyMode) { + desired = { + x: wish.x * flySpeed * dt, + y: upDown * flySpeed * dt, + z: wish.z * flySpeed * dt, + }; + if (characterController && playerCollider) { + characterController.computeColliderMovement( + playerCollider, + desired, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS + ); + const m = characterController.computedMovement(); + const mx = m.x, my = m.y, mz = m.z; + playerBody.setNextKinematicTranslation({ x: t.x + mx, y: t.y + my, z: t.z + mz }); + } else { + playerBody.setNextKinematicTranslation({ x: t.x + desired.x, y: t.y + desired.y, z: t.z + desired.z }); + } + } else { + walkVerticalVel -= gravity * dt; + + if (keys.up && characterController?.computedGrounded?.()) { + walkVerticalVel = jumpVel; + } + + desired = { x: wish.x * speed * dt, y: walkVerticalVel * dt, z: wish.z * speed * dt }; + + if (characterController && playerCollider) { + characterController.computeColliderMovement( + playerCollider, + desired, + RAPIER.QueryFilterFlags.EXCLUDE_SENSORS + ); + const m = characterController.computedMovement(); + const mx = m.x, my = m.y, mz = m.z; + const grounded = characterController.computedGrounded(); + if (grounded && walkVerticalVel < 0) walkVerticalVel = 0; + playerBody.setNextKinematicTranslation({ x: t.x + mx, y: t.y + my, z: t.z + mz }); + } else { + playerBody.setNextKinematicTranslation({ x: t.x + desired.x, y: t.y + desired.y, z: t.z + desired.z }); + } + } + + // Safety: if Ghost is OFF, ensure the collider is not a sensor + try { + if (!ghostMode && playerCollider && typeof playerCollider.isSensor === "function" && playerCollider.isSensor()) { + playerCollider.setSensor(false); + } + } catch {} + avatar.position.set(p.x, p.y, p.z); + + // If agent camera follow is active, DON'T sync player camera to player body + // The tick() function will handle camera positioning via updateAgentCameraFollow + if (!agentCameraFollow) { + controls.object.position.set(p.x, p.y + PLAYER_EYE_HEIGHT, p.z); + } + + // Expose player position for other modules (AI, etc). + if (typeof window !== "undefined") { + window.__playerPosition = [p.x, p.y, p.z]; + } +} + +function tick() { + const rawDt = clock.getDelta(); + const physicsDt = Math.min(rawDt, 0.05); + const motionDt = Math.min(rawDt, 0.02); + + updateRapier(physicsDt); + + // Bumpable assets: only compute if any exist + if (_hasBumpableAssets()) { + const agentPushers = aiAgents.length ? collectAgentBumpPushers(physicsDt) : []; + let bumpPlayerPos = null; + if (playerBody) { + const p = playerBody.translation(); + bumpPlayerPos = new THREE.Vector3(p.x, p.y, p.z); + } else { + bumpPlayerPos = controls.object.position.clone(); + bumpPlayerPos.y -= PLAYER_EYE_HEIGHT; + } + updateBumpableAssets(physicsDt, bumpPlayerPos, agentPushers); + } + + // Update AI agents (if Rapier is initialized). + if (aiAgents.length && rapierWorld) { + const now = Date.now(); + for (const a of aiAgents) { + try { + // Keep cmd_vel integration tied to wall-clock delta even when physics dt is clamped. + a.update(motionDt, now); + } catch (e) { + console.warn("AI update failed:", e); + } + } + } + + // Update agent camera follow (after agent update, before render) + if (agentCameraFollow) { + updateAgentCameraFollow(physicsDt); + avatar.visible = false; + } + + // Update interaction hint at reduced rate + const now = performance.now(); + if (now - _lastHintUpdate > 300) { + _lastHintUpdate = now; + updateInteractionHint(); + } + + // LiDAR / sensor overlays — run when explicitly enabled OR in dimos mode + // In dimos mode, always skip browser raycasting — server handles lidar via Rapier snapshots. + const _skipBrowserLidar = dimosMode; + if (!_skipBrowserLidar && (simSensorViewMode === "lidar" || simCompareView || dimosMode)) { + lidarVizGroup.visible = true; + updateLidarPointCloud(); + if (_lidarGeom.drawRange.count <= 0 && _lidarLastNonZeroDrawCount > 0) { + _lidarGeom.setDrawRange(0, _lidarLastNonZeroDrawCount); + } + // In dimos mode, hide LiDAR viz from the main scene render — it's only + // needed for data capture + the sidebar LiDAR panel renders it separately. + if (dimosMode && simSensorViewMode !== "lidar" && !simCompareView) { + lidarVizGroup.visible = false; + } + } else if (!_skipBrowserLidar && rgbdPcOverlayOnLidar && (simSensorViewMode === "lidar" || simCompareView)) { + updateRgbdPcOverlayCloud(false); + } + + if (!_skipBrowserLidar) pushLidarPoseSample(); + + // Dimos sensor capture — GPU readback needs rAF, odom runs independently via setInterval + if (dimosMode && window.__dimosBridge) { + const bridge = window.__dimosBridge; + if (bridge._connected) { + // Lidar: skip browser→WS publish when server-side lidar is active + if (bridge._dirty.lidar && !bridge._serverLidar) { + bridge._dirty.lidar = false; + bridge._publishLidar(); + } + // Camera stream disabled for now. + // if (bridge._dirty.images) { + // bridge._dirty.images = false; + // bridge._publishImages(); + // } + } + } + + // Agent vision captures + if (hasPendingCapture()) { + processPendingCaptures().then(() => { + renderActiveView(); + requestAnimationFrame(tick); + }); + return; + } + + renderActiveView(); + requestAnimationFrame(tick); +} + +// Interaction hint elements (cached) +let _lastHintUpdate = 0; +let _crosshairEl = null; +let _interactionHintEl = null; + +function updateInteractionHint() { + // Cache DOM elements + if (!_crosshairEl) _crosshairEl = document.getElementById("crosshair"); + if (!_interactionHintEl) _interactionHintEl = document.getElementById("interaction-hint"); + + // Only show when pointer is locked and no popup is visible + if (!controls?.isLocked || isInteractionPopupVisible()) { + _crosshairEl?.classList.remove("interactable"); + if (_interactionHintEl) { + _interactionHintEl.classList.remove("visible"); + } + return; + } + + // If holding something, show drop hint + if (playerHeldAsset) { + const heldAsset = getPlayerHeldAsset(); + const heldName = heldAsset?.title || "item"; + _crosshairEl?.classList.remove("interactable"); + _crosshairEl?.classList.add("holding"); + if (_interactionHintEl) { + _interactionHintEl.innerHTML = `Holding: ${escapeHtml(heldName)} · DropE`; + _interactionHintEl.classList.add("visible"); + } + return; + } + if (playerHeldGroupId) { + const heldGroup = groups.find((g) => g.id === playerHeldGroupId); + const heldName = heldGroup?.name || "group"; + _crosshairEl?.classList.remove("interactable"); + _crosshairEl?.classList.add("holding"); + if (_interactionHintEl) { + _interactionHintEl.innerHTML = `Holding: ${escapeHtml(heldName)} · DropE`; + _interactionHintEl.classList.add("visible"); + } + return; + } + + // Not holding anything - remove holding class + _crosshairEl?.classList.remove("holding"); + + const target = getInteractableAssetAtCrosshair(); + + if (target) { + const { kind, asset, group, actions, dist, canPickUp } = target; + const title = kind === "group" ? (group?.name || "(group)") : (asset.title || "(asset)"); + + // Build action description + let actionText; + if (kind === "group") { + actionText = "Pick up"; + } else if (actions.length === 0 && canPickUp) { + actionText = "Pick up"; + } else if (actions.length === 1 && !canPickUp) { + actionText = actions[0].label || "interact"; + } else { + const count = actions.length + (canPickUp ? 1 : 0); + actionText = `${count} actions`; + } + + _crosshairEl?.classList.add("interactable"); + if (_interactionHintEl) { + const cycleHint = kind === "asset" && target.candidateCount > 1 + ? ` · Cycle ${target.candidateIndex + 1}/${target.candidateCount}R` + : ""; + _interactionHintEl.innerHTML = `${escapeHtml(title)} · ${escapeHtml(actionText)}E${cycleHint}`; + _interactionHintEl.classList.add("visible"); + } + } else { + _crosshairEl?.classList.remove("interactable"); + if (_interactionHintEl) { + _interactionHintEl.classList.remove("visible"); + } + } +} + +setStatus("Select a .ply/.spz to start."); +tick(); + +// Expose debug utilities +window.clearWorldStorage = clearWorldStorage; +window.__robovalLidar = { + // Returns the latest standardized frames (raw + deskewed + optional range image) + getLatestFrames() { + return { + raw: _lidarLatestRawFrame, + deskewed: _lidarLatestDeskewedFrame, + rangeImage: _lidarLatestRangeImage, + }; + }, + // ROS2 PointCloud2-compatible dict converter + toPointCloud2(frame) { + return to_pointcloud2(frame); + }, + // Manual export of the latest frame set to NPZ files. + async exportLatest() { + if (!_lidarLatestRawFrame || !_lidarLatestDeskewedFrame) return false; + await writeLidarFrameFiles(_lidarLatestRawFrame, _lidarLatestDeskewedFrame, _lidarLatestRangeImage); + return true; + }, + // Auto-export each LiDAR frame (warning: downloads many files in browser). + setAutoExport(enabled) { + _lidarAutoExport = !!enabled; + return _lidarAutoExport; + }, + getAutoExport() { + return _lidarAutoExport; + }, + // Force a known-good synthetic cloud to isolate renderer issues from sensor math. + setKnownGoodDebugCloud(enabled) { + _lidarUseKnownGoodDebugCloud = !!enabled; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + return _lidarUseKnownGoodDebugCloud; + }, + getKnownGoodDebugCloud() { + return _lidarUseKnownGoodDebugCloud; + }, + // Toggle ordered scan debug render (single-frame, lidar-frame) vs accumulated world cloud. + setOrderedDebugView(enabled) { + lidarOrderedDebugView = !!enabled; + if (!lidarOrderedDebugView) { + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + } + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + return lidarOrderedDebugView; + }, + getOrderedDebugView() { + return lidarOrderedDebugView; + }, + setNoiseModel(enabled) { + lidarNoiseEnabled = !!enabled; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + return lidarNoiseEnabled; + }, + getNoiseModel() { + return lidarNoiseEnabled; + }, + setMultiReturnMode(mode) { + lidarMultiReturnMode = mode === "last" ? "last" : "strongest"; + _lidarAccumFrames.length = 0; + _lidarLastAccumPose = null; + resetLidarScanState(); + updateSimSensorButtons(); + if (simSensorViewMode === "lidar") updateLidarPointCloud(); + return lidarMultiReturnMode; + }, + getMultiReturnMode() { + return lidarMultiReturnMode; + }, +}; + +window.__robovalRgbd = { + // Returns metric camera-space Z depth map in meters (Float32Array length W*H). + // Uses the same render path as on-screen RGB-D mode. + getMetricDepthFrame() { + renderRgbdView(); + const depth = readRgbdMetricDepthFrameMeters(); + if (!depth) return null; + return { + width: rgbdMetricTarget.width, + height: rgbdMetricTarget.height, + depth_m: depth, + semantics: "camera_space_z", + units: "meters", + min_depth_m: RGBD_MIN_DEPTH_M, + max_depth_m: RGBD_MAX_DEPTH_M, + }; + }, + getOverlayStats() { + return { + enabled: rgbdPcOverlayOnLidar, + visible: rgbdPcOverlayGroup.visible, + points: _rgbdPcOverlayLastCount, + rt_w: RGBD_PC_OVERLAY_RT_W, + rt_h: RGBD_PC_OVERLAY_RT_H, + dirty: _rgbdPcOverlayDirty, + }; + }, +}; + +// Debug: List all colliders in the physics world +window.debugColliders = function() { + if (!rapierWorld) { + console.log("[DEBUG] No physics world loaded"); + return; + } + + console.log("[DEBUG] === ALL COLLIDERS IN PHYSICS WORLD ==="); + let count = 0; + rapierWorld.colliders.forEach((collider) => { + const pos = collider.translation(); + const shape = collider.shape; + const isSensor = collider.isSensor(); + const handle = collider.handle; + console.log(`Collider #${count} (handle=${handle}): pos=(${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)}), sensor=${isSensor}, shapeType=${shape.type}`); + count++; + }); + console.log(`[DEBUG] Total colliders: ${count}`); + + // Also show asset collider handles + console.log("[DEBUG] === ASSET COLLIDERS (on asset objects) ==="); + let assetColCount = 0; + for (const a of assets) { + if (a._colliderHandle) { + const handleInfo = typeof a._colliderHandle === 'object' ? `obj.handle=${a._colliderHandle.handle}` : `num=${a._colliderHandle}`; + console.log(` ${a.id}: "${a.title}", _colliderHandle=${handleInfo}`); + assetColCount++; + } + } + console.log(`[DEBUG] Assets with colliders: ${assetColCount}`); + + // Show tracked map + console.log("[DEBUG] === _assetColliderHandles Map ==="); + console.log(`Map size: ${_assetColliderHandles.size}`); +}; + +// Debug: Remove all colliders except world/player +window.debugClearAssetColliders = function() { + if (!rapierWorld) return; + + // Helper to remove a collider (handles both object and number) + const removeCol = (handle) => { + try { + if (typeof handle === 'object' && handle.handle !== undefined) { + rapierWorld.removeCollider(handle, true); + return true; + } else if (typeof handle === 'number') { + const collider = rapierWorld.getCollider(handle); + if (collider) { + rapierWorld.removeCollider(collider, true); + return true; + } + } + } catch (e) {} + return false; + }; + + let removed = 0; + + // Remove all tracked asset colliders + _assetColliderHandles.forEach((handle, assetId) => { + if (removeCol(handle)) removed++; + }); + _assetColliderHandles.clear(); + + // Also clear colliders stored on asset objects + for (const asset of assets) { + if (asset._colliderHandle != null) { + if (removeCol(asset._colliderHandle)) removed++; + asset._colliderHandle = null; + } + } + + console.log(`[DEBUG] Cleared ${removed} asset colliders`); +}; + +// ── dimos integration mode boot ────────────────────────────────────────────── +// When dimosMode is active, auto-load a scene and spawn an agent, then connect +// the LCM bridge so sensor data flows and external /odom drives the agent. +if (dimosMode) { + (async () => { + try { + // 1. Auto-load scene + const sceneName = dimosScene || "apt"; + console.log(`[dimos] Loading scene: ${sceneName}`); + const resp = await fetch(`/sims/${sceneName}.json`); + if (!resp.ok) throw new Error(`Scene fetch failed: HTTP ${resp.status}`); + const sceneJson = await resp.json(); + await importLevelFromJSON(sceneJson); + console.log(`[dimos] Scene loaded: ${sceneName}`); + + // 2. Auto-spawn agent (wait for physics to settle) + await new Promise((r) => setTimeout(r, 1500)); + await ensureRapierLoaded(); + // Re-apply grid floor now that rapier is loaded (creates collider) + spawnPlayerInsideScene(); + const agent = createAiAgent({ ephemeral: false }); + aiAgents.push(agent); + // Place agent at a default spawn point + const spawnPos = sceneJson.dimosSpawnPoint || { x: 2, y: 0.5, z: 3 }; + agent.setPosition(spawnPos.x, spawnPos.y, spawnPos.z); + renderAgentTaskUi(); // update UI: hide spawn button, enable task controls + // Server-side physics: agent pose is driven by ServerPhysics (Deno). + // Browser just receives position updates and moves the visual avatar. + let _dimosYaw = 0; + // Bridge updates _dimosYaw via this setter when server sends pose + window.__dimosSetYaw = (yaw) => { _dimosYaw = yaw; }; + agent.update = function(_dt) { + this._syncVisual(); + }; + console.log(`[dimos] Agent spawned: ${agent.id}`); + + // 3. Set up fixed-size offscreen capture for dimos. + // Keep sensor cost independent of the headed browser window size. + const _dimosCapW = 640, _dimosCapH = 288; + const _dimosCapTarget = new THREE.WebGLRenderTarget(_dimosCapW, _dimosCapH, { + minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, depthBuffer: true, stencilBuffer: false, + }); + // Go2 depth camera: 87° horizontal. At 640x288 (2.22:1 aspect), that's 46° vertical. + const _dimosFov = window.__dimosCameraFov || 46; + const _dimosCapCam = new THREE.PerspectiveCamera(_dimosFov, _dimosCapW / _dimosCapH, camera.near, camera.far); + const _dimosCapBuf = new Uint8Array(_dimosCapW * _dimosCapH * 4); + const _dimosCapCvs = document.createElement("canvas"); + _dimosCapCvs.width = _dimosCapW; + _dimosCapCvs.height = _dimosCapH; + const _dimosCapCtx = _dimosCapCvs.getContext("2d"); + const _dimosDepthTarget = new THREE.WebGLRenderTarget(_dimosCapW, _dimosCapH, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + type: THREE.UnsignedByteType, + depthBuffer: true, + stencilBuffer: false, + }); + _dimosDepthTarget.texture.generateMipmaps = false; + _dimosDepthTarget.depthTexture = new THREE.DepthTexture(_dimosCapW, _dimosCapH, THREE.UnsignedIntType); + _dimosDepthTarget.depthTexture.minFilter = THREE.NearestFilter; + _dimosDepthTarget.depthTexture.magFilter = THREE.NearestFilter; + _dimosDepthTarget.depthTexture.generateMipmaps = false; + const _dimosMetricTarget = new THREE.WebGLRenderTarget(_dimosCapW, _dimosCapH, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: rgbdMetricUsesR32F ? THREE.RedFormat : THREE.RGBAFormat, + type: rgbdMetricTargetType, + depthBuffer: false, + stencilBuffer: false, + }); + if (rgbdMetricUsesR32F) _dimosMetricTarget.texture.internalFormat = "R32F"; + _dimosMetricTarget.texture.generateMipmaps = false; + + function _dimosReadMetricDepthFrameMeters() { + const w = _dimosMetricTarget.width; + const h = _dimosMetricTarget.height; + if (!w || !h) return null; + + if (rgbdMetricUsesR32F) { + const depth = new Float32Array(w * h); + renderer.readRenderTargetPixels(_dimosMetricTarget, 0, 0, w, h, depth); + return depth; + } + + if (_dimosMetricTarget.texture.type === THREE.FloatType) { + const raw = new Float32Array(w * h * 4); + renderer.readRenderTargetPixels(_dimosMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = raw[i * 4 + 0]; + return depth; + } + + const raw = new Uint16Array(w * h * 4); + renderer.readRenderTargetPixels(_dimosMetricTarget, 0, 0, w, h, raw); + const depth = new Float32Array(w * h); + for (let i = 0; i < w * h; i++) depth[i] = halfToFloat(raw[i * 4 + 0]); + return depth; + } + + function _dimosCaptureRgb() { + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch), sp = Math.sin(pitch); + const feetY = ay - ((agent.halfHeight || 0.25) + (agent.radius || 0.12)); + const eyeY = feetY + GO2_CAMERA_HEIGHT; + const eyeX = ax + Math.sin(yaw) * GO2_CAMERA_FORWARD; + const eyeZ = az + Math.cos(yaw) * GO2_CAMERA_FORWARD; + _dimosCapCam.position.set(eyeX, eyeY, eyeZ); + _dimosCapCam.lookAt(eyeX + Math.sin(yaw)*cp, eyeY + sp, eyeZ + Math.cos(yaw)*cp); + _dimosCapCam.updateProjectionMatrix(); + _dimosCapCam.updateMatrixWorld(true); + + const prev = renderer.getRenderTarget(); + const prevAgentVisible = agent.group?.visible; + if (agent.group) agent.group.visible = false; + renderer.setRenderTarget(_dimosCapTarget); + renderer.render(scene, _dimosCapCam); + renderer.setRenderTarget(prev); + if (agent.group) agent.group.visible = prevAgentVisible; + + renderer.readRenderTargetPixels(_dimosCapTarget, 0, 0, _dimosCapW, _dimosCapH, _dimosCapBuf); + // Flip Y — return raw RGBA pixels (no JPEG encode) + const flipped = new Uint8Array(_dimosCapW * _dimosCapH * 4); + const rowB = _dimosCapW * 4; + for (let y = 0; y < _dimosCapH; y++) { + flipped.set(_dimosCapBuf.subarray((_dimosCapH-1-y)*rowB, (_dimosCapH-y)*rowB), y*rowB); + } + return { data: flipped, width: _dimosCapW, height: _dimosCapH }; + } + + // Offscreen depth capture from agent POV using a dedicated low-res target. + function _dimosCaptureDepth() { + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + const yaw = agent.group?.rotation?.y ?? 0; + const pitch = typeof agent.pitch === "number" ? agent.pitch : 0; + const cp = Math.cos(pitch), sp = Math.sin(pitch); + const feetY = ay - ((agent.halfHeight || 0.25) + (agent.radius || 0.12)); + const eyeY = feetY + GO2_CAMERA_HEIGHT; + const eyeX = ax + Math.sin(yaw) * GO2_CAMERA_FORWARD; + const eyeZ = az + Math.cos(yaw) * GO2_CAMERA_FORWARD; + _dimosCapCam.position.set(eyeX, eyeY, eyeZ); + _dimosCapCam.lookAt(eyeX + Math.sin(yaw)*cp, eyeY + sp, eyeZ + Math.cos(yaw)*cp); + _dimosCapCam.updateProjectionMatrix(); + _dimosCapCam.updateMatrixWorld(true); + + const prevDepthTex = rgbdMetricMaterial.uniforms.uDepthTex.value; + const prevNear = rgbdMetricMaterial.uniforms.uNear.value; + const prevFar = rgbdMetricMaterial.uniforms.uFar.value; + const prevNoise = rgbdMetricMaterial.uniforms.uNoiseEnabled.value; + const prevSpeckle = rgbdMetricMaterial.uniforms.uSpeckleEnabled.value; + rgbdMetricMaterial.uniforms.uDepthTex.value = _dimosDepthTarget.depthTexture; + rgbdMetricMaterial.uniforms.uNear.value = _dimosCapCam.near; + rgbdMetricMaterial.uniforms.uFar.value = _dimosCapCam.far; + rgbdMetricMaterial.uniforms.uNoiseEnabled.value = rgbdNoiseEnabled ? 1.0 : 0.0; + rgbdMetricMaterial.uniforms.uSpeckleEnabled.value = rgbdSpeckleEnabled ? 1.0 : 0.0; + + const savedOverride = scene.overrideMaterial; + const savedAssets = assetsGroup.visible; + const savedPrims = primitivesGroup.visible; + const savedLights = lightsGroup.visible; + const savedTags = tagsGroup.visible; + const savedLidarViz = lidarVizGroup.visible; + const savedRgbdPc = rgbdPcOverlayGroup.visible; + + scene.overrideMaterial = null; + assetsGroup.visible = true; + primitivesGroup.visible = true; + lightsGroup.visible = true; + tagsGroup.visible = false; + lidarVizGroup.visible = false; + rgbdPcOverlayGroup.visible = false; + const savedAgentVisible = agent.group?.visible; + if (agent.group) agent.group.visible = false; + + renderer.setRenderTarget(_dimosDepthTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(scene, _dimosCapCam); + + renderer.setRenderTarget(_dimosMetricTarget); + renderer.setClearColor(0x000000, RGBD_CLEAR_ALPHA); + renderer.clear(true, true, true); + renderer.render(rgbdMetricScene, rgbdPostCamera); + + scene.overrideMaterial = savedOverride; + assetsGroup.visible = savedAssets; + primitivesGroup.visible = savedPrims; + lightsGroup.visible = savedLights; + tagsGroup.visible = savedTags; + lidarVizGroup.visible = savedLidarViz; + rgbdPcOverlayGroup.visible = savedRgbdPc; + if (agent.group) agent.group.visible = savedAgentVisible; + rgbdMetricMaterial.uniforms.uDepthTex.value = prevDepthTex; + rgbdMetricMaterial.uniforms.uNear.value = prevNear; + rgbdMetricMaterial.uniforms.uFar.value = prevFar; + rgbdMetricMaterial.uniforms.uNoiseEnabled.value = prevNoise; + rgbdMetricMaterial.uniforms.uSpeckleEnabled.value = prevSpeckle; + renderer.setRenderTarget(null); + + const depthData = _dimosReadMetricDepthFrameMeters(); + if (!depthData) return null; + + const dw = _dimosMetricTarget.width, dh = _dimosMetricTarget.height; + + // Flip rows: WebGL reads bottom-to-top, image convention is top-to-bottom + const flipped = new Float32Array(dw * dh); + for (let y = 0; y < dh; y++) { + flipped.set(depthData.subarray((dh - 1 - y) * dw, (dh - y) * dw), y * dw); + } + return { data: flipped, width: dw, height: dh }; + } + + // 4. Sidebar sensor panel setup (depth + LiDAR canvases) + const _dimosSidebarW = 320, _dimosSidebarH = 145; + const _dimosDepthCanvas = document.getElementById("agent-depth-canvas"); + const _dimosLidarCanvas = document.getElementById("agent-lidar-canvas"); + if (_dimosDepthCanvas) { _dimosDepthCanvas.width = _dimosSidebarW; _dimosDepthCanvas.height = _dimosSidebarH; } + if (_dimosLidarCanvas) { _dimosLidarCanvas.width = _dimosSidebarW; _dimosLidarCanvas.height = _dimosSidebarH; } + const _dimosDepthCtx = _dimosDepthCanvas?.getContext("2d"); + const _dimosLidarCtx = _dimosLidarCanvas?.getContext("2d"); + + // Small offscreen render targets for sidebar panels + const _dimosSidebarDepthTarget = new THREE.WebGLRenderTarget(_dimosSidebarW, _dimosSidebarH, { + minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, depthBuffer: true, stencilBuffer: false, + }); + const _dimosSidebarLidarTarget = new THREE.WebGLRenderTarget(_dimosSidebarW, _dimosSidebarH, { + minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, depthBuffer: true, stencilBuffer: false, + }); + const _dimosSidebarReadBuf = new Uint8Array(_dimosSidebarW * _dimosSidebarH * 4); + + // Helper: render a target, readback, flip Y, draw to 2D canvas + function _dimosBlitToCanvas(rt, ctx, w, h) { + renderer.readRenderTargetPixels(rt, 0, 0, w, h, _dimosSidebarReadBuf); + const flipped = new Uint8ClampedArray(w * h * 4); + const rowB = w * 4; + for (let y = 0; y < h; y++) { + flipped.set(_dimosSidebarReadBuf.subarray((h-1-y)*rowB, (h-y)*rowB), y*rowB); + } + ctx.putImageData(new ImageData(flipped, w, h), 0, 0); + } + + /** Update the sidebar sensor panels (called after capture) */ + function _dimosUpdateSidebarPanels(rgbBase64) { + if (window.__dimosHeadless) return; + + // RGB — set img src + if (rgbBase64 && agentShotImgEl) { + agentShotImgEl.src = `data:image/jpeg;base64,${rgbBase64}`; + } + + // Depth — render colormap to small target, blit to canvas + if (_dimosDepthCtx) { + const prev = renderer.getRenderTarget(); + rgbdVizMaterial.uniforms.uGrayMode.value = rgbdVizMode === "gray" ? 1.0 : 0.0; + renderer.setRenderTarget(_dimosSidebarDepthTarget); + renderer.setClearColor(0x000000, 1); + renderer.clear(true, true, true); + renderer.render(rgbdVizScene, rgbdPostCamera); + _dimosBlitToCanvas(_dimosSidebarDepthTarget, _dimosDepthCtx, _dimosSidebarW, _dimosSidebarH); + renderer.setRenderTarget(prev); + } + + // LiDAR — render lidar scene from agent POV to small target, blit to canvas + if (_dimosLidarCtx) { + const prev = renderer.getRenderTarget(); + // Save/restore scene visibility for lidar-only render + const savedAssets = assetsGroup.visible; + const savedPrims = primitivesGroup.visible; + const savedLights = lightsGroup.visible; + const savedTags = tagsGroup.visible; + const savedLidar = lidarVizGroup.visible; + const savedOverlay = rgbdPcOverlayGroup.visible; + const savedBg = scene.background; + + assetsGroup.visible = false; + primitivesGroup.visible = false; + lightsGroup.visible = false; + tagsGroup.visible = false; + lidarVizGroup.visible = true; + rgbdPcOverlayGroup.visible = false; + scene.background = RGBD_BG; + + renderer.setRenderTarget(_dimosSidebarLidarTarget); + renderer.setClearColor(0x000000, 1); + renderer.clear(true, true, true); + renderer.render(scene, _dimosCapCam); + + // Restore + assetsGroup.visible = savedAssets; + primitivesGroup.visible = savedPrims; + lightsGroup.visible = savedLights; + tagsGroup.visible = savedTags; + lidarVizGroup.visible = savedLidar; + rgbdPcOverlayGroup.visible = savedOverlay; + scene.background = savedBg; + + _dimosBlitToCanvas(_dimosSidebarLidarTarget, _dimosLidarCtx, _dimosSidebarW, _dimosSidebarH); + renderer.setRenderTarget(prev); + } + } + + // 5. Connect dimos bridge + let _lastRgbBase64 = null; + const { DimosBridge } = await import("./dimos/dimosBridge.ts"); + const bridge = new DimosBridge({ + agent, + rates: window.__dimosSensorRates || undefined, + sensorEnable: window.__dimosSensorEnable || undefined, + sensorSources: { + captureRgb: () => { + const frame = _dimosCaptureRgb(); + if (!frame) return null; + // Render to canvas → JPEG (used for both LCM publish and eval/sidebar) + _dimosCapCtx.putImageData(new ImageData(new Uint8ClampedArray(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength), frame.width, frame.height), 0, 0); + const dataUrl = _dimosCapCvs.toDataURL("image/jpeg", 0.75); + _lastRgbBase64 = dataUrl.split("base64,")[1] || null; + if (!_lastRgbBase64) return null; + // Decode base64 → Uint8Array for JPEG LCM transport + const bin = atob(_lastRgbBase64); + const jpegBytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) jpegBytes[i] = bin.charCodeAt(i); + return { data: jpegBytes, width: frame.width, height: frame.height }; + }, + captureDepth: () => _dimosCaptureDepth(), + captureLidar: () => { + // Return world-frame points (Three.js Y-up). + // Bridge converts Y-up → ROS Z-up and labels frame_id="world". + const lLen = _lidarLatestWorldPts ? _lidarLatestWorldPts.length : -1; + if (lLen > 0) { + return { + points: _lidarLatestWorldPts, + intensity: _lidarLatestWorldIntensity, + numPoints: lLen / 3, + }; + } + const frames = window.__robovalLidar?.getLatestFrames?.(); + const src = frames?.raw; + if (!src) return null; + return { points: src.points, intensity: src.intensity, numPoints: src.points?.length / 3 || 0 }; + }, + getOdomPose: () => { + const pos = agent.getPosition?.(); + if (!pos) return null; // skip this frame instead of fallback to origin + const [ax, ay, az] = pos; + const qw = Math.cos(_dimosYaw / 2); + const qy = Math.sin(_dimosYaw / 2); + return { x: ax, y: ay, z: az, qx: 0, qy, qz: 0, qw }; + }, + }, + }); + + // Hook: after _publishSensors — RGB capture disabled for now (GPU stall) + // _dimosCaptureRgb() does a full render + readRenderTargetPixels which + // stalls the GPU pipeline. Skip it for lidar-only nav testing. + // const origPublishSensors = bridge._publishSensors.bind(bridge); + // bridge._publishSensors = function() { + // origPublishSensors(); + // _lastRgbBase64 = _dimosCaptureRgb(); + // _dimosUpdateSidebarPanels(_lastRgbBase64); + // }; + + bridge.connect(); + bridge.sceneReady = true; + window.__dimosBridge = bridge; + window.__dimosCapCam = _dimosCapCam; + window.__dimosAgent = agent; + + // Send Rapier world snapshot to bridge server for server-side physics + lidar. + // Flush any deferred collider builds first — primitives (floor, walls) may be + // queued in _pendingColliderBuilds if they were created before the render loop ran. + flushPendingColliderBuilds(); + let _snapshotColliders = 0; + rapierWorld.colliders.forEach(() => { _snapshotColliders++; }); + console.log(`[dimos] Flushed collider queue — ${_snapshotColliders} colliders in world before snapshot`); + + // Format: [DSSN 4B][spawnX f32][spawnY f32][spawnZ f32][snapshot...] + const _waitSensorWs = () => { + if (bridge.wsSensors && bridge.wsSensors.readyState === WebSocket.OPEN) { + try { + const snapshot = rapierWorld.takeSnapshot(); + const [sx, sy, sz] = agent.getPosition?.() || [2, 0.5, 3]; + const prefixed = new Uint8Array(16 + snapshot.byteLength); + const dv = new DataView(prefixed.buffer); + dv.setUint32(0, 0x44535332, false); // "DSS2" — includes spawn position + dv.setFloat32(4, sx, true); + dv.setFloat32(8, sy, true); + dv.setFloat32(12, sz, true); + prefixed.set(snapshot, 16); + bridge.wsSensors.send(prefixed.buffer); + bridge._serverLidar = true; + console.log(`[DimosBridge] sent Rapier snapshot (${(snapshot.byteLength / 1024).toFixed(0)}KB) spawn=(${sx.toFixed(1)},${sy.toFixed(1)},${sz.toFixed(1)}) — server physics + lidar active`); + } catch (e) { + console.warn("[DimosBridge] snapshot send failed:", e); + } + } else { + setTimeout(_waitSensorWs, 200); + } + }; + _waitSensorWs(); + // Expose yaw for lidar pose sampling (avoids reading Three.js Euler) + Object.defineProperty(window, '__dimosYaw', { get: () => _dimosYaw }); + + // Odom: server-side physics publishes odom directly to LCM. + // Browser no longer needs to publish odom — server is authoritative. + + // Eval harness — scores objectDistance rubric when triggered by dimsim eval runner + const { EvalHarness } = await import("./dimos/evalHarness.ts"); + const channel = new URLSearchParams(location.search).get("channel") || undefined; + const evalHarness = new EvalHarness({ + bridge, + channel, + getSceneState: () => { + const enriched = assets.map(a => { + const obj = assetsGroup.getObjectByName(`asset:${a.id}`); + if (obj) { + const bbox = new THREE.Box3().setFromObject(obj); + if (!bbox.isEmpty()) { + const center = new THREE.Vector3(); + const size = new THREE.Vector3(); + bbox.getCenter(center); + bbox.getSize(size); + return { + ...a, + transform: { x: center.x, y: center.y, z: center.z }, + _bbox: { w: size.x, h: size.y, d: size.z }, + }; + } + } + return a; + }); + return { assets: enriched }; + }, + getAgentPose: () => { + const pos = agent.getPosition?.(); + if (!pos) return null; + const camOffset = 0.3; + const cx = pos[0] + Math.sin(_dimosYaw) * camOffset; + const cz = pos[2] + Math.cos(_dimosYaw) * camOffset; + return { x: cx, y: pos[1], z: cz, yaw: _dimosYaw, pitch: 0 }; + }, + }); + window.__evalHarness = evalHarness; + + // Scene editor — script execution engine for sim editing (exec_js API) + const { SceneEditor } = await import("./dimos/sceneEditor.ts"); + const sceneEditor = new SceneEditor({ + bridge, + channel, + globals: { scene, THREE, RAPIER, rapierWorld, worldBody, renderer, camera, agent, assets, assetsGroup, gltfLoader }, + }); + window.__sceneEditor = sceneEditor; + + // Agent POV only in headless (sensor capture needs it). Headed = free orbit. + if (window.__dimosHeadless) { + enableAgentCameraFollow(agent.id); + } + + // 7a. dimos mode UI cleanup handled in CSS via body.dimos-mode class + // (panel hiding) and .shortcuts-floating in index.html (WASD strip). + + // 7b. Debug panel (integration diagnostics) — hidden for now + if (false && !window.__dimosHeadless) { + const dbg = document.createElement("div"); + dbg.id = "dimos-debug"; + dbg.style.cssText = "position:fixed;bottom:8px;left:8px;z-index:99999;background:rgba(0,0,0,0.88);color:#0f0;font:11px/1.4 monospace;padding:10px 14px;border-radius:8px;max-width:460px;max-height:400px;overflow-y:auto;pointer-events:auto;user-select:text;"; + document.body.appendChild(dbg); + + const _dbgState = { + bridgeConn: false, + sensorFps: 0, + agentPos: { x: 0, y: 0, z: 0 }, + agentYaw: 0, + cmdVel: { angY: 0, linZ: 0 }, + _sensorCount: 0, + _sensorLastTs: Date.now(), + }; + + // Hook lidar publish for FPS counter + const _origPubLidar2 = bridge._publishLidar; + bridge._publishLidar = function() { + _dbgState._sensorCount++; + _origPubLidar2.call(bridge); + }; + + // Update loop + setInterval(() => { + const now = Date.now(); + const dt = (now - _dbgState._sensorLastTs) / 1000; + if (dt >= 1) { + _dbgState.sensorFps = Math.round(_dbgState._sensorCount / dt); + _dbgState._sensorCount = 0; + _dbgState._sensorLastTs = now; + } + + const [ax, ay, az] = agent.getPosition?.() || [0, 0, 0]; + _dbgState.agentPos = { x: ax.toFixed(2), y: ay.toFixed(2), z: az.toFixed(2) }; + _dbgState.agentYaw = (agent.group?.rotation?.y ?? 0).toFixed(3); + _dbgState.bridgeConn = bridge.ws?.readyState === WebSocket.OPEN; + const vel = bridge.getCmdVel(); + _dbgState.cmdVel = { angY: vel.angY.toFixed(3), linZ: vel.linZ.toFixed(3) }; + + dbg.innerHTML = ` +
dimos integration
+
Bridge: ${_dbgState.bridgeConn ? 'connected' : 'disconnected'} | Sensors: ${_dbgState.sensorFps} fps
+
Agent: (${_dbgState.agentPos.x}, ${_dbgState.agentPos.y}, ${_dbgState.agentPos.z}) yaw=${_dbgState.agentYaw}
+
cmd_vel: angY=${_dbgState.cmdVel.angY} linZ=${_dbgState.cmdVel.linZ}
+ `; + }, 500); + } + + console.log("[dimos] Bridge connected. Sensor publishing active."); + } catch (err) { + console.error("[dimos] Initialization failed:", err); + } + })(); +} diff --git a/misc/DimSim/src/main.js b/misc/DimSim/src/main.js new file mode 100644 index 0000000000..f237e9a49d --- /dev/null +++ b/misc/DimSim/src/main.js @@ -0,0 +1,4 @@ +// DimSim engine entry point. +// Imports the full engine from ./engine.js (copied from SimStudio/src/main.js). +// To update: bash copy-sources.sh +import "./engine.js"; diff --git a/misc/DimSim/src/style.css b/misc/DimSim/src/style.css new file mode 100644 index 0000000000..5ae5a2e7c4 --- /dev/null +++ b/misc/DimSim/src/style.css @@ -0,0 +1,3188 @@ +/* ============================================================================ + SimStudio - Professional Training Environment + ============================================================================ */ + +:root { + color-scheme: dark; + + /* Core Colors — flat terminal/cyberdeck */ + --bg-primary: #06080b; + --bg-secondary: #0b0e13; + --bg-tertiary: #10141b; + --bg-elevated: rgba(9, 12, 17, 0.96); + + /* Surface Colors */ + --surface-1: rgba(255, 255, 255, 0.02); + --surface-2: rgba(255, 255, 255, 0.04); + --surface-3: rgba(255, 255, 255, 0.07); + + /* Border Colors — hairline */ + --border-subtle: rgba(255, 255, 255, 0.06); + --border-default: rgba(255, 255, 255, 0.10); + --border-strong: rgba(255, 255, 255, 0.16); + + /* Text Colors */ + --text-primary: rgba(255, 255, 255, 0.95); + --text-secondary: rgba(255, 255, 255, 0.68); + --text-tertiary: rgba(255, 255, 255, 0.44); + + /* Accent Colors — dimos-style cool cyan-white */ + --accent-primary: #d6dde6; + --accent-primary-hover: #ffffff; + --accent-primary-subtle: rgba(214, 221, 230, 0.14); + --accent-primary-glow: rgba(214, 221, 230, 0.28); + + --accent-success: #7fe3c4; + --accent-success-subtle: rgba(127, 227, 196, 0.14); + + --accent-warning: #f5c06b; + --accent-warning-subtle: rgba(245, 192, 107, 0.12); + + --accent-info: #7fd4f5; + --accent-info-subtle: rgba(127, 212, 245, 0.12); + + /* Shadows — flat, no heavy drop shadows */ + --shadow-sm: 0 0 0 1px rgba(0, 0, 0, 0.2); + --shadow-md: 0 0 0 1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 0 0 1px rgba(0, 0, 0, 0.4); + --shadow-xl: 0 0 0 1px rgba(0, 0, 0, 0.5); + + /* Transitions */ + --transition-fast: 0.1s linear; + --transition-normal: 0.15s linear; + --transition-slow: 0.2s linear; + + /* Border Radius — sharp corners */ + --radius-sm: 0px; + --radius-md: 0px; + --radius-lg: 0px; + --radius-xl: 2px; + + /* Font Stacks */ + --font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'SF Mono', 'Fira Code', 'Menlo', 'Monaco', 'Consolas', ui-monospace, monospace; + --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +/* ============================================================================ + Base Styles + ============================================================================ */ + +html, body { + height: 100%; + margin: 0; +} + +body { + overflow: hidden; + font-family: var(--font-sans); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 13px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Google Font Import */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'); + +/* ============================================================================ + Canvas + ============================================================================ */ + +#c { + position: fixed; + inset: 0; + width: 100%; + height: 100%; + display: block; +} + +/* ============================================================================ + Overlay Layout + ============================================================================ */ + +#overlay { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 100; + padding: 0; + box-sizing: border-box; + display: grid; + grid-template-columns: 330px 1fr 380px; + grid-template-rows: auto 1fr; + grid-template-areas: + "top top top" + "left . right"; + gap: 0; +} + +/* Collapsed states for edit mode panels */ +#overlay.left-collapsed { + grid-template-columns: 0 1fr 380px; +} +#overlay.right-collapsed { + grid-template-columns: 330px 1fr 0; +} +#overlay.left-collapsed.right-collapsed { + grid-template-columns: 0 1fr 0; +} + +html[data-mode="sim"] #overlay { + grid-template-columns: 1fr 340px; + grid-template-rows: 1fr; + grid-template-areas: ". panel"; + padding: 16px; + gap: 16px; +} + +html[data-mode="sim"] #overlay.sim-panel-collapsed { + grid-template-columns: 1fr 0; +} + +/* ============================================================================ + Slim Toolbar (UE-inspired) + ============================================================================ */ + +#overlay-top { + pointer-events: auto; + grid-area: top; + display: flex; + gap: 4px; + align-items: center; + padding: 4px 8px; + background: #1a1d24; + border-bottom: 1px solid rgba(255,255,255,0.08); + box-shadow: 0 2px 8px rgba(0,0,0,0.4); + flex-wrap: wrap; + overflow: visible; + min-height: 36px; + position: relative; + z-index: 10; +} + +/* Toolbar generic button */ +.tb-btn { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 10px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--text-secondary); + background: transparent; + border: 1px solid transparent; + border-radius: 0; + cursor: pointer; + white-space: nowrap; + transition: all 0.1s linear; +} +.tb-btn:hover { background: rgba(255,255,255,0.04); color: var(--text-primary); border-color: var(--border-default); } +.tb-btn.active { color: var(--accent-primary); background: var(--accent-primary-subtle); border-color: var(--accent-primary); } +.tb-btn.tb-primary { background: transparent; color: var(--accent-primary); border: 1px solid var(--accent-primary); } +.tb-btn.tb-primary:hover { background: var(--accent-primary-subtle); color: var(--accent-primary-hover); border-color: var(--accent-primary-hover); } +.tb-btn.tb-danger { color: #ef4444; } +.tb-btn.tb-danger:hover { background: rgba(239,68,68,0.12); color: #f87171; } +.tb-btn.tb-muted { color: var(--text-tertiary); font-weight: 500; } +.tb-btn.tb-muted:hover { color: var(--text-secondary); } +.tb-btn svg { flex-shrink: 0; } + +/* File upload styled as toolbar button */ +.tb-file-label { cursor: pointer; } +.tb-file-label input[type="file"] { display: none; } + +/* Toolbar select */ +.tb-select { + padding: 4px 8px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + background: rgba(255,255,255,0.03); + border: 1px solid var(--border-default); + border-radius: 0; + color: var(--text-primary); + cursor: pointer; + outline: none; + max-width: 130px; +} +.tb-select:focus { border-color: var(--accent-primary); } + +/* Toolbar separator */ +.tb-sep { + width: 1px; + height: 20px; + background: rgba(255,255,255,0.1); + margin: 0 2px; + flex-shrink: 0; +} +.tb-spacer { flex: 1; } + +/* Toolbar transform group */ +.tb-transform { + display: flex; + gap: 2px; + padding: 2px; + background: rgba(255,255,255,0.02); + border: 1px solid var(--border-subtle); + border-radius: 0; +} + +/* Toolbar status */ +.tb-status { + font-size: 11px; + color: var(--text-tertiary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; +} +.tb-status:empty { display: none; } + +/* Toolbar group */ +.tb-group { display: flex; gap: 3px; align-items: center; } + +.workspace-tab-strip { + display: inline-flex; + gap: 0; + padding: 0; + background: transparent; + border: 1px solid var(--border-default); + border-radius: 0; + margin: 0 4px; +} +.workspace-tab-strip .tb-btn { + padding: 4px 12px; + font-size: 11px; + font-weight: 500; + border-radius: 0; + border: none; + color: var(--text-tertiary); + transition: all 0.1s linear; +} +.workspace-tab-strip .tb-btn:hover { + color: var(--text-secondary); + background: rgba(255,255,255,0.04); +} +.workspace-tab-strip .tb-btn.active { + color: var(--accent-primary-hover); + background: var(--accent-primary-subtle); + box-shadow: inset 0 -2px 0 var(--accent-primary); +} + +/* Builder-mode inline shape palette */ +.builder-shape-bar { + display: inline-flex; + gap: 2px; + padding: 2px; + background: rgba(255,255,255,0.02); + border: 1px solid var(--border-subtle); + border-radius: 0; +} +.builder-shape-bar .tb-btn { + padding: 4px 8px; + font-size: 12px; + gap: 3px; +} +.builder-shape-bar .shape-icon { + font-size: 11px; + opacity: 0.7; +} + +/* Advanced dropdown */ +.tb-advanced { position: relative; } +.tb-advanced > summary { list-style: none; } +.tb-advanced > summary::-webkit-details-marker { display: none; } +.tb-advanced-body { + position: absolute; + top: calc(100% + 4px); + right: 0; + z-index: 50; + background: var(--bg-secondary); + border: 1px solid var(--border-default); + border-radius: 0; + padding: 4px; + display: flex; + gap: 4px; + box-shadow: none; +} + +/* Legacy world-selector compat (now .tb-group) */ +.world-selector { display: contents; } + +/* ============================================================================ + Buttons + ============================================================================ */ + +button, +.file span { + cursor: pointer; + user-select: none; + padding: 7px 14px; + border-radius: 0; + border: 1px solid var(--border-default); + background: transparent; + color: var(--text-primary); + font-weight: 500; + font-size: 12px; + font-family: var(--font-mono); + letter-spacing: 0.05em; + text-transform: uppercase; + transition: all var(--transition-fast); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +/* Toolbar buttons override the heavy defaults */ +.tb-btn, #overlay-top button, #overlay-top .file span { + padding: 5px 10px; + border-radius: 0; + border: 1px solid transparent; + background: transparent; + font-size: 11px; + font-family: var(--font-mono); + transform: none !important; +} + +button:hover, +.file span:hover { + border-color: var(--accent-primary); + background: var(--accent-primary-subtle); + color: var(--accent-primary-hover); +} + +button:active, +.file span:active { + transform: none; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +/* Primary Action Button */ +button.primary { + background: transparent; + border: 1px solid var(--accent-primary); + color: var(--accent-primary); + box-shadow: none; +} + +button.primary:hover { + background: var(--accent-primary-subtle); + border-color: var(--accent-primary-hover); + color: var(--accent-primary-hover); + box-shadow: none; +} + +/* Portal Button */ +#portal-create-btn { + background: var(--surface-2); + border: 1px solid var(--border-default); + color: var(--text-primary); + box-shadow: none; +} + +#portal-create-btn:hover { + background: var(--surface-3); + border-color: var(--border-strong); + box-shadow: var(--shadow-sm); +} + +/* Portal Exit Modal */ +#portal-exit-modal .modal-title { + background: var(--surface-1); +} + +#portal-exit-modal .modal-hint { + border-left-color: var(--accent-primary); + background: var(--accent-primary-subtle); +} + +#portal-exit-world-name { + color: var(--text-primary); + font-weight: 700; +} + +/* Watermark logo */ +.watermark-logo { + position: fixed; + bottom: 16px; + right: 16px; + height: 32px; + width: auto; + opacity: 0.35; + pointer-events: none; + z-index: 9999; + user-select: none; +} + +/* ============================================ + PORTAL LOADING SCREEN + ============================================ */ +.portal-loading { + position: fixed; + inset: 0; + z-index: 10000; + background: radial-gradient(ellipse at center, #1a1035 0%, #0a0a0f 70%, #000 100%); + display: flex; + align-items: center; + justify-content: center; + opacity: 1; + transition: opacity 0.5s ease-out; +} + +.portal-loading.hidden { + opacity: 0; + pointer-events: none; +} + +.portal-loading.fade-out { + opacity: 0; +} + +.portal-loading-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 40px; +} + +.portal-loading-effect { + position: relative; + width: 150px; + height: 150px; +} + +.portal-ring { + position: absolute; + inset: 0; + border-radius: 50%; + border: 3px solid transparent; + border-top-color: #8b5cf6; + border-bottom-color: #a78bfa; + animation: portal-spin 1.5s linear infinite; +} + +.portal-ring:nth-child(2) { + inset: 15px; + border-top-color: #7c3aed; + border-bottom-color: #8b5cf6; + animation-duration: 2s; + animation-direction: reverse; +} + +.portal-ring:nth-child(3) { + inset: 30px; + border-top-color: #6d28d9; + border-bottom-color: #7c3aed; + animation-duration: 2.5s; +} + +@keyframes portal-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Inner glow effect */ +.portal-loading-effect::before { + content: ''; + position: absolute; + inset: 40px; + border-radius: 50%; + background: radial-gradient(circle, rgba(139, 92, 246, 0.4) 0%, rgba(109, 40, 217, 0.2) 50%, transparent 70%); + animation: portal-pulse 2s ease-in-out infinite; +} + +@keyframes portal-pulse { + 0%, 100% { + transform: scale(0.9); + opacity: 0.6; + } + 50% { + transform: scale(1.1); + opacity: 1; + } +} + +/* Particle effect */ +.portal-loading-effect::after { + content: ''; + position: absolute; + inset: 20px; + border-radius: 50%; + background: transparent; + box-shadow: + 0 0 30px rgba(139, 92, 246, 0.5), + 0 0 60px rgba(139, 92, 246, 0.3), + 0 0 90px rgba(139, 92, 246, 0.1); + animation: portal-glow 2s ease-in-out infinite alternate; +} + +@keyframes portal-glow { + 0% { + box-shadow: + 0 0 30px rgba(139, 92, 246, 0.5), + 0 0 60px rgba(139, 92, 246, 0.3), + 0 0 90px rgba(139, 92, 246, 0.1); + } + 100% { + box-shadow: + 0 0 40px rgba(167, 139, 250, 0.6), + 0 0 80px rgba(139, 92, 246, 0.4), + 0 0 120px rgba(139, 92, 246, 0.2); + } +} + +.portal-loading-text { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + text-align: center; +} + +#portal-loading-title { + font-size: 18px; + font-weight: 600; + color: #e2e8f0; + letter-spacing: 1px; +} + +#portal-loading-dest { + font-size: 24px; + font-weight: 700; + color: #a78bfa; + text-shadow: 0 0 20px rgba(139, 92, 246, 0.5); +} + +/* Success Button */ +button.success { + background: linear-gradient(135deg, var(--accent-success) 0%, #16a34a 100%); + border: none; + color: white; +} + +/* Danger Button */ +button.danger { + background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); + border: none; + color: white; +} + +/* Ghost Button */ +button.ghost { + background: transparent; + border-color: transparent; + color: var(--text-secondary); +} + +button.ghost:hover { + background: var(--surface-2); + color: var(--text-primary); +} + +.file input { + display: none; +} + +/* ============================================================================ + Form Elements + ============================================================================ */ + +input[type="text"], +input[type="email"], +input[type="password"], +textarea, +.select { + width: 100%; + box-sizing: border-box; + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--border-default); + background: var(--surface-2); + color: var(--text-primary); + font-weight: 500; + font-size: 14px; + outline: none; + transition: all var(--transition-fast); +} + +input[type="text"]:hover, +textarea:hover, +.select:hover { + border-color: var(--border-strong); +} + +input[type="text"]:focus, +textarea:focus, +.select:focus { + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px var(--accent-primary-subtle); +} + +input[type="text"]::placeholder, +textarea::placeholder { + color: var(--text-tertiary); +} + +textarea { + resize: vertical; + min-height: 100px; + font-family: inherit; + line-height: 1.5; +} + +/* Checkbox Styling */ +input[type="checkbox"] { + width: 18px; + height: 18px; + border-radius: var(--radius-sm); + border: 2px solid var(--border-strong); + background: var(--surface-2); + cursor: pointer; + appearance: none; + -webkit-appearance: none; + transition: all var(--transition-fast); + position: relative; +} + +input[type="checkbox"]:checked { + background: var(--accent-primary); + border-color: var(--accent-primary); +} + +input[type="checkbox"]:checked::after { + content: "✓"; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 12px; + font-weight: 700; +} + +input[type="checkbox"]:hover { + border-color: var(--accent-primary); +} + +/* ============================================================================ + Status Bar + ============================================================================ */ + +.status { + margin-left: auto; + padding: 6px 12px; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + background: var(--surface-1); + border-radius: var(--radius-sm); + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ============================================================================ + Details / Accordion + ============================================================================ */ + +.details { + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background: var(--surface-1); + padding: 0; + overflow: hidden; +} + +.details > summary { + cursor: pointer; + user-select: none; + font-weight: 600; + color: var(--text-secondary); + font-size: 12px; + padding: 10px 14px; + list-style: none; + transition: all var(--transition-fast); + display: flex; + align-items: center; + gap: 8px; +} + +.details > summary::-webkit-details-marker { + display: none; +} + +.details > summary::before { + content: "▸"; + font-size: 10px; + transition: transform var(--transition-fast); +} + +.details[open] > summary::before { + transform: rotate(90deg); +} + +.details > summary:hover { + background: var(--surface-2); + color: var(--text-primary); +} + +.details-body { + padding: 12px 14px; + border-top: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + gap: 12px; +} + +.details-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +/* ============================================================================ + Slider + ============================================================================ */ + +.slider { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 0; +} + +.slider-label { + font-size: 10px; + color: var(--text-tertiary); + font-weight: 600; + min-width: 40px; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.slider input[type="range"] { + flex: 1; + height: 3px; + border-radius: 2px; + background: rgba(255,255,255,0.1); + appearance: none; + -webkit-appearance: none; +} + +.slider input[type="range"]::-webkit-slider-thumb { + appearance: none; + -webkit-appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent-primary); + cursor: pointer; + border: 1.5px solid rgba(255,255,255,0.3); +} + +.slider-value { + font-variant-numeric: tabular-nums; + font-weight: 600; + font-size: 10px; + color: var(--text-secondary); + min-width: 30px; + text-align: right; +} + + +/* ============================================================================ + Side Panel (UE-inspired) + ============================================================================ */ + +.side-panel { + pointer-events: auto; + width: 100%; + height: calc(100vh - 36px); /* below toolbar */ + display: flex; + flex-direction: column; + background: #1a1d24; + overflow: hidden; +} + +.side-panel-left { + grid-area: left; + border-right: 1px solid rgba(255,255,255,0.06); +} + +.side-panel-right { + grid-area: right; + border-left: 1px solid rgba(255,255,255,0.06); +} + +/* Hide panels when overlay is collapsed */ +#overlay.left-collapsed .side-panel-left { + display: none; +} +#overlay.right-collapsed .side-panel-right { + display: none; +} + +html[data-mode="sim"] .side-panel { + grid-area: panel; + height: calc(100vh - 32px); + border-radius: 0; + border: 1px solid var(--border-default); + backdrop-filter: blur(20px); + background: var(--bg-elevated); +} + +.panel-header { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 10px; + border-bottom: 1px solid rgba(255,255,255,0.06); + background: rgba(255,255,255,0.02); +} + +.panel-title { + font-family: var(--font-mono); + font-weight: 600; + font-size: 11px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-secondary); + display: flex; + align-items: center; + gap: 8px; +} + +.panel-title::before { + content: "["; + color: var(--accent-primary); + font-family: var(--font-mono); + font-weight: 500; + font-size: 12px; + background: transparent; + width: auto; + height: auto; + border-radius: 0; + letter-spacing: 0; +} +.panel-title::after { + content: "]"; + color: var(--accent-primary); + font-family: var(--font-mono); + font-weight: 500; + font-size: 12px; + margin-left: 2px; + letter-spacing: 0; +} + +.mode-toggle { + padding: 3px 8px !important; + font-size: 11px !important; + font-weight: 500 !important; + font-family: var(--font-mono) !important; + background: transparent !important; + border: 1px solid var(--border-default) !important; + color: var(--text-tertiary) !important; + display: flex !important; + align-items: center !important; + gap: 5px !important; + border-radius: 0 !important; + cursor: pointer; + transition: all 0.1s !important; + text-transform: none !important; + letter-spacing: 0 !important; +} +.mode-toggle:hover { background: rgba(255,255,255,0.06) !important; color: var(--text-primary) !important; } +.mode-icon { font-size: 9px; } + +.panel-content { + flex: 1; + overflow-y: auto; + overflow-x: hidden; +} + +.panel-footer { + flex-shrink: 0; + padding: 5px 10px; + border-top: 1px solid rgba(255,255,255,0.06); + background: rgba(255,255,255,0.02); +} + +/* Panel collapse/expand buttons */ +.panel-collapse-btn { + padding: 4px 8px !important; + font-size: 10px !important; + font-weight: 500 !important; + font-family: var(--font-mono) !important; + background: transparent !important; + border: 1px solid var(--border-default) !important; + color: var(--text-tertiary) !important; + border-radius: 0 !important; + cursor: pointer; + transition: all 0.1s !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + line-height: 1 !important; +} +.panel-collapse-btn:hover { background: rgba(255,255,255,0.06) !important; color: var(--text-primary) !important; } + +/* Left panel open button (shown when collapsed) */ +.left-panel-open { + pointer-events: auto; + position: fixed; + top: 44px; + left: 8px; + width: 30px; + height: 30px; + border-radius: 0; + border: 1px solid var(--border-default); + background: var(--bg-elevated); + color: var(--text-secondary); + cursor: pointer; + z-index: 150; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); +} +.left-panel-open:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); } +.left-panel-open.hidden { display: none; } + +/* Right AI panel open button (shown when collapsed) */ +.ai-panel-open { + pointer-events: auto; + position: fixed; + top: 44px; + right: 8px; + width: 30px; + height: 30px; + border-radius: 0; + border: 1px solid var(--border-default); + background: var(--bg-elevated); + color: var(--text-secondary); + cursor: pointer; + z-index: 150; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); +} +.ai-panel-open:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); } +.ai-panel-open.hidden { display: none; } + +.sim-panel-open { + pointer-events: auto; + position: fixed; + top: 16px; + right: 16px; + width: 30px; + height: 30px; + border-radius: 0; + border: 1px solid var(--border-default); + background: var(--bg-elevated); + color: var(--text-secondary); + cursor: pointer; + z-index: 150; + font-family: var(--font-mono); +} +.sim-panel-open:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); } +.sim-panel-open.hidden { display: none; } + +.shortcuts { + display: flex; + flex-wrap: wrap; + gap: 6px 10px; + font-size: 10px; + font-family: var(--font-mono); + letter-spacing: 0.04em; + color: var(--text-tertiary); +} + +.shortcuts b { + color: var(--accent-primary); + font-weight: 500; + background: transparent; + border: 1px solid var(--border-default); + padding: 0 4px; + border-radius: 0; + font-size: 9px; + font-family: var(--font-mono); +} + +/* Floating bottom-left shortcuts strip (always visible in sim mode) */ +.shortcuts-floating { + position: fixed; + bottom: 16px; + left: 16px; + padding: 8px 12px; + background: rgba(8, 10, 14, 0.82); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 0; + font-size: 11px; + letter-spacing: 0.06em; + text-transform: uppercase; + z-index: 9998; + backdrop-filter: blur(4px); +} +.shortcuts-floating b { + font-size: 10px; + padding: 1px 5px; +} + +/* Dimos-only: hide the sim side panel and command bar — leave only + top-left status + bottom-left WASD shortcuts visible. */ +body.dimos-mode #agent-panel, +body.dimos-mode #sim-panel-open, +body.dimos-mode #agent-command-bar { + display: none !important; +} + +/* Tag form / tag-selected (legacy compat) */ +.tag-selected { display: none; } +.tag-form { /* managed by details-panel */ } +.tag-form.hidden { display: none; } + +/* Floating status for sim mode */ +.status-floating { + position: fixed; + top: 16px; + left: 16px; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.04em; + color: var(--text-secondary); + background: var(--bg-elevated); + border: 1px solid var(--border-default); + border-radius: 0; + backdrop-filter: blur(12px); + pointer-events: auto; + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.status-floating:empty { display: none; } + +/* ============================================================================ + Outliner (Scene tree, collapsible sections) + ============================================================================ */ + +.outliner { + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ol-section { + border-bottom: 1px solid rgba(255,255,255,0.04); +} +.ol-section:last-child { border-bottom: none; } +.ol-section > summary { list-style: none; } +.ol-section > summary::-webkit-details-marker { display: none; } + +.ol-header { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 10px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-secondary); + cursor: pointer; + user-select: none; + transition: background 0.1s; +} +.ol-header:hover { background: rgba(255,255,255,0.04); } + +.ol-header::before { + content: "▸"; + font-size: 9px; + color: var(--text-tertiary); + transition: transform 0.15s; + display: inline-block; + width: 10px; +} +.ol-section[open] > .ol-header::before { transform: rotate(90deg); } + +.ol-icon { font-size: 12px; } +.ol-count { + margin-left: auto; + font-size: 10px; + font-weight: 500; + color: var(--text-tertiary); + background: rgba(255,255,255,0.04); + padding: 1px 6px; + border-radius: 0; + min-width: 16px; + text-align: center; + font-family: var(--font-mono); +} + +.ol-list { + display: flex; + flex-direction: column; + gap: 1px; + padding: 0 0 2px 0; + max-height: 160px; + overflow-y: auto; +} + +/* Compact outliner items (replaces old .tag-item) */ +.tag-item, .ol-item { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px 4px 26px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + transition: all 0.08s; + border: none; + background: transparent; + border-radius: 0; +} + +.tag-item:hover { + background: rgba(255,255,255,0.04); + color: var(--text-primary); +} + +.tag-item small { + margin-left: auto; + color: var(--text-tertiary); + font-size: 10px; + font-weight: 400; + margin-top: 0; + display: inline; + flex-shrink: 0; +} + +.tag-item.active { + background: var(--accent-primary-subtle); + color: var(--accent-primary-hover); + border: none; + box-shadow: inset 3px 0 0 var(--accent-primary); +} + +/* ============================================================================ + Groups in Outliner + ============================================================================ */ + +.ol-group { + border-left: 2px solid transparent; + margin-bottom: 2px; +} +.ol-group.active { + border-left-color: var(--accent-primary); + background: rgba(99,102,241,0.04); +} + +.ol-group-header { + display: flex; + align-items: center; + gap: 5px; + padding: 4px 8px 4px 14px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + transition: background 0.08s; + user-select: none; +} +.ol-group-collapse-btn { + width: 18px; + height: 18px; + padding: 0; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: var(--text-tertiary); + font-size: 11px; + line-height: 1; + cursor: pointer; +} +.ol-group-collapse-btn:hover { + background: rgba(255,255,255,0.06); + color: var(--text-primary); +} +.ol-group-header:hover { + background: rgba(255,255,255,0.05); + color: var(--text-primary); +} + +.ol-group-icon { + font-size: 11px; + flex-shrink: 0; +} +.ol-group-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ol-group-count { + font-size: 10px; + font-weight: 400; + color: var(--text-tertiary); + flex-shrink: 0; +} +.ol-group-pickable { + font-size: 11px; + flex-shrink: 0; + opacity: 0.85; +} + +.ol-group-actions { + display: flex; + gap: 2px; + margin-left: auto; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.1s; +} +.ol-group-header:hover .ol-group-actions { + opacity: 1; +} + +.ol-group-btn { + padding: 1px 6px; + font-size: 10px; + font-weight: 600; + font-family: inherit; + border: 1px solid var(--border-default); + border-radius: var(--radius-sm); + background: var(--surface-2); + color: var(--text-tertiary); + cursor: pointer; + transition: all 0.08s; +} +.ol-group-btn:hover { + background: var(--surface-3); + color: var(--text-primary); + border-color: var(--border-strong); +} + +.ol-group-children { + padding-left: 12px; + border-left: 1px solid var(--border-subtle); + margin-left: 18px; +} +.ol-group-child { + padding-left: 14px !important; + font-size: 11px !important; +} + +/* Multi-select (shift+click) */ +.tag-item.multi-selected { + background: rgba(139, 92, 246, 0.12); + color: var(--text-primary); + box-shadow: inset 3px 0 0 #8B5CF6; +} +.tag-item.multi-selected:hover { + background: rgba(139, 92, 246, 0.18); +} + +/* Group action bar (shown when 2+ shapes are shift-selected) */ +.ol-group-action-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: rgba(139, 92, 246, 0.08); + border: 1px solid rgba(139, 92, 246, 0.25); + border-radius: var(--radius-md); + margin: 4px 6px; +} +.ol-group-action-label { + font-size: 11px; + font-weight: 600; + color: #a78bfa; + flex: 1; + white-space: nowrap; +} +.ol-group-action-btn { + padding: 3px 10px; + font-size: 11px; + font-weight: 600; + font-family: inherit; + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + white-space: nowrap; + transition: all 0.1s; + background: #8B5CF6; + color: #fff; +} +.ol-group-action-btn:hover { + background: #7C3AED; +} +.ol-group-cancel-btn { + background: var(--surface-2); + color: var(--text-secondary); + border: 1px solid var(--border-default); +} +.ol-group-cancel-btn:hover { + background: var(--surface-3); +} + +/* ============================================================================ + Details Panel (Properties of selected object) + ============================================================================ */ + +.details-panel { + border-top: 2px solid rgba(99,102,241,0.3); +} +.details-panel.hidden { display: none; } + +.details-title { + padding: 6px 10px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--accent-primary-hover); + background: rgba(99,102,241,0.06); + border-bottom: 1px solid rgba(255,255,255,0.04); +} + +/* Details collapsible sections */ +.dt-section { + border-bottom: 1px solid rgba(255,255,255,0.04); +} +.dt-section > summary { list-style: none; } +.dt-section > summary::-webkit-details-marker { display: none; } + +.dt-header { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 10px; + font-size: 11px; + font-weight: 600; + color: var(--text-tertiary); + cursor: pointer; + user-select: none; + text-transform: uppercase; + letter-spacing: 0.03em; +} +.dt-header:hover { color: var(--text-secondary); } +.dt-header::before { + content: "▸"; + font-size: 9px; + transition: transform 0.15s; + display: inline-block; + width: 10px; +} +.dt-section[open] > .dt-header::before { transform: rotate(90deg); } + +.dt-body { + padding: 6px 10px 8px; +} + +/* Inputs inside details */ +.dt-input { + width: 100%; + padding: 5px 8px; + background: rgba(0,0,0,0.3); + border: 1px solid var(--border-default); + border-radius: 0; + color: var(--text-primary); + font-family: var(--font-mono); + font-size: 12px; + margin-bottom: 6px; + transition: border-color 0.1s; + resize: vertical; +} +.dt-input:focus { outline: none; border-color: var(--accent-primary); } + +.dt-select { + flex: 1; + padding: 4px 8px; + background: rgba(0,0,0,0.3); + border: 1px solid var(--border-default); + border-radius: 0; + color: var(--text-primary); + font-family: var(--font-mono); + font-size: 12px; +} + +.dt-row { + display: flex; + gap: 6px; + align-items: center; + margin-bottom: 6px; +} + +/* Asset-builder transition rows: keep remove button visible in narrow panels */ +.builder-interaction-row { + flex-wrap: wrap; +} +.builder-interaction-row .builder-transition-label { + flex: 1 1 180px; + min-width: 150px; + margin-bottom: 0; +} +.builder-interaction-row .builder-transition-target { + flex: 0 1 140px; + min-width: 120px; +} +.builder-interaction-row .builder-transition-remove { + flex: 0 0 auto; +} + +.dt-actions { + display: flex; + gap: 4px; +} +.dt-actions .tb-btn { flex: 1; justify-content: center; } + +/* ============================================================================ + Transform XYZ Grid (UE-style) + ============================================================================ */ + +.xform-row { + display: grid; + grid-template-columns: 56px 1fr 1fr 1fr; + gap: 3px; + margin-bottom: 3px; + align-items: center; +} + +.xform-label { + font-size: 10px; + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.03em; + padding-left: 2px; +} + +.xform-in { + width: 100%; + padding: 4px 6px; + font-size: 11px; + font-family: var(--font-mono); + font-weight: 500; + background: rgba(0,0,0,0.3); + border: 1px solid var(--border-subtle); + border-radius: 0; + color: var(--text-primary); + text-align: right; + -moz-appearance: textfield; +} +.xform-in::-webkit-outer-spin-button, +.xform-in::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } +.xform-in:focus { outline: none; border-color: rgba(255,255,255,0.2); } + +/* Color-coded X/Y/Z borders (UE style) */ +.xform-x { border-left: 2px solid #ef4444; } +.xform-y { border-left: 2px solid #22c55e; } +.xform-z { border-left: 2px solid #3b82f6; } + +/* Legacy compat: hide old elements that are now in toolbar */ +.panel-section { padding: 0; border: none; } +.section-label { display: none; } +.asset-tools { display: none; /* now in toolbar */ } + +/* ============================================================================ + Modal + ============================================================================ */ + +.modal { + pointer-events: auto; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + display: grid; + place-items: center; + z-index: 200; + animation: modalFadeIn 0.2s ease; +} + +@keyframes modalFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.modal-card { + width: min(560px, calc(100vw - 48px)); + border-radius: 0; + border: 1px solid var(--border-default); + background: var(--bg-secondary); + box-shadow: none; + overflow: hidden; + max-height: calc(100vh - 96px); + display: flex; + flex-direction: column; + animation: modalSlideIn 0.3s ease; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.modal-title { + padding: 16px 20px; + font-weight: 500; + font-size: 13px; + font-family: var(--font-mono); + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--accent-primary); + border-bottom: 1px solid var(--border-subtle); + background: var(--surface-1); +} + +.modal-body { + padding: 20px; + overflow-y: auto; +} + +.modal-body input, +.modal-body textarea { + margin-bottom: 12px; +} + +.modal-subtitle { + margin: 16px 0 10px; + font-size: 12px; + color: var(--text-secondary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.modal-hint { + margin-top: 12px; + padding: 12px 14px; + font-size: 12px; + color: var(--text-tertiary); + line-height: 1.5; + background: var(--surface-1); + border-radius: var(--radius-md); + border-left: 3px solid var(--accent-info); +} + +.modal-hint b { + color: var(--text-secondary); +} + +/* Asset States */ +.asset-states { + display: flex; + flex-direction: column; + gap: 10px; +} + +.asset-state-row { + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + padding: 14px; + background: var(--surface-1); + display: flex; + flex-direction: column; + gap: 10px; +} + +.asset-state-row-top { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; +} + +.asset-state-row-top label { + font-size: 12px; + color: var(--text-secondary); + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +} + +.asset-interactions-title { + font-size: 11px; + color: var(--text-tertiary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-top: 4px; +} + +.asset-interactions { + display: flex; + flex-direction: column; + gap: 8px; +} + +.asset-interaction-row { + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; +} + +.asset-interaction-row input[type="text"] { + flex: 1; + min-width: 140px; + margin: 0; + padding: 8px 12px; + font-size: 13px; +} + +/* ============================================================================ + Agent Panel Components + ============================================================================ */ + +/* ---------- Floating Command Bar ---------- */ +.agent-cmd-bar { + pointer-events: auto; + position: fixed; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: var(--bg-elevated); + border: 1px solid var(--border-default); + border-radius: 0; + backdrop-filter: blur(16px); + box-shadow: none; + z-index: 160; + max-width: min(600px, calc(100vw - 32px)); + transition: opacity var(--transition-fast); + opacity: 0.75; +} +.agent-cmd-bar:hover, +.agent-cmd-bar:focus-within, +.agent-cmd-bar.active { + opacity: 1; +} + +.agent-cmd-spawn { + flex-shrink: 0; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + background: transparent; + color: var(--text-primary); + border: 1px solid var(--border-default); + border-radius: 0; + cursor: pointer; + white-space: nowrap; + transition: all var(--transition-fast); + font-family: var(--font-mono); +} +.agent-cmd-spawn:hover { + background: var(--accent-primary-subtle); + border-color: var(--accent-primary); + color: var(--accent-primary-hover); +} + +.agent-cmd-status { + flex-shrink: 0; + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + white-space: nowrap; + max-width: 80px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.agent-cmd-status:empty { + display: none; +} + +.agent-cmd-input { + flex: 1; + min-width: 140px; + padding: 6px 10px; + font-size: 12px; + font-weight: 500; + font-family: var(--font-mono); + background: rgba(0,0,0,0.35); + color: var(--text-primary); + border: 1px solid var(--border-default); + border-radius: 0; + outline: none; + transition: border-color var(--transition-fast); +} +.agent-cmd-input:focus { + border-color: var(--accent-primary); +} +.agent-cmd-input::placeholder { + color: var(--text-tertiary); +} + +.agent-cmd-btn { + flex-shrink: 0; + padding: 6px 14px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.06em; + text-transform: uppercase; + background: transparent; + color: var(--accent-primary); + border: 1px solid var(--accent-primary); + border-radius: 0; + cursor: pointer; + white-space: nowrap; + transition: all var(--transition-fast); +} +.agent-cmd-btn:hover { + background: var(--accent-primary-subtle); + color: var(--accent-primary-hover); + border-color: var(--accent-primary-hover); +} +.agent-cmd-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.agent-cmd-stop { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border-default); +} +.agent-cmd-stop:hover { + background: rgba(239,68,68,0.08); + border-color: #ef4444; + color: #f87171; +} + +.agent-control-strip { + padding: 8px 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); +} +.agent-control-selected { + font-size: 11px; + font-weight: 600; + color: var(--text-tertiary); + margin-bottom: 6px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.agent-control-actions { + display: flex; + gap: 6px; +} +.agent-control-actions .tb-btn { + flex: 1; + justify-content: center; + padding: 5px 8px; + font-size: 11px; +} +.agent-control-task-row { + margin-top: 6px; + display: flex; + gap: 6px; +} +.agent-control-task-input { + flex: 1; + min-width: 0; + padding: 6px 8px; + font-size: 11px; + color: var(--text-primary); + background: var(--surface-1); + border: 1px solid var(--border-default); + border-radius: var(--radius-sm); + outline: none; +} +.agent-control-task-input:focus { + border-color: var(--accent-primary); +} + +.agent-badge-layer { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 170; +} +.agent-badge { + position: fixed; + transform: translate(-50%, -50%); + pointer-events: auto; + z-index: 220; + touch-action: none; + padding: 3px 8px; + font-size: 10px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-primary); + background: rgba(16, 22, 32, 0.9); + border: 1px solid var(--border-strong); + border-radius: 0; + cursor: pointer; + white-space: nowrap; + box-shadow: none; +} +.agent-badge.active { + border-color: var(--accent-primary); + box-shadow: 0 0 0 2px var(--accent-primary-subtle), var(--shadow-md); +} + +/* ===== Vibe Creator – Right Panel Layout ===== */ +.vibe-panel-header { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 10px; + border-bottom: 1px solid rgba(255,255,255,0.06); + background: rgba(255,255,255,0.02); +} + +.vibe-panel-title { + font-weight: 600; + font-size: 11px; + font-family: var(--font-mono); + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-secondary); + display: flex; + align-items: center; + gap: 6px; +} +.vibe-panel-title::before { + content: ">"; + font-family: var(--font-mono); + font-size: 12px; + color: var(--accent-primary); +} + +.vibe-tabs { + display: flex; + gap: 4px; + padding: 6px 10px; + border-bottom: 1px solid var(--border-subtle); + background: rgba(255,255,255,0.01); +} +.vibe-tab-btn { + padding: 5px 10px; + border-radius: 0; + border: 1px solid transparent; + background: transparent; + color: var(--text-tertiary); + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.08em; + text-transform: uppercase; + cursor: pointer; +} +.vibe-tab-btn:hover { + color: var(--text-secondary); + background: rgba(255,255,255,0.04); +} +.vibe-tab-btn.active { + color: var(--text-primary); + border-color: rgba(139, 92, 246, 0.35); + background: rgba(139, 92, 246, 0.15); +} + +.vibe-content { + flex: 1; + min-height: 0; + display: flex; +} +.vibe-tab-pane { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +.vibe-assets-help { + font-size: 11px; + color: var(--text-tertiary); + line-height: 1.6; + padding: 10px; + border-bottom: 1px solid var(--border-subtle); +} +.vibe-asset-controls { + display: flex; + gap: 6px; + padding: 10px; + border-bottom: 1px solid var(--border-subtle); +} +.vibe-asset-aux-controls { + display: flex; + gap: 6px; + padding: 8px 10px; + border-bottom: 1px solid var(--border-subtle); +} +.vibe-asset-aux-controls .vibe-btn { + flex: 1; + padding: 6px 8px; + font-size: 11px; + border: 1px solid var(--border-default); + background: var(--surface-2); + color: var(--text-secondary); +} +.vibe-asset-aux-controls .vibe-btn:hover { + background: var(--surface-3); + color: var(--text-primary); +} +.vibe-asset-list { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 8px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); + grid-auto-rows: max-content; + align-content: start; + align-items: start; + gap: 10px; +} +.vibe-asset-empty { + font-size: 12px; + color: var(--text-tertiary); + padding: 16px 10px; +} +.vibe-asset-item { + border: 1px solid var(--border-subtle); + border-radius: 0; + background: rgba(255,255,255,0.02); + padding: 6px; + height: auto; + align-self: start; +} +.vibe-asset-thumb { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + border-radius: 0; + border: 1px solid rgba(255,255,255,0.08); + background: #0b0d11; + display: block; +} +.vibe-asset-item-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin: 6px 2px 0; +} +.vibe-asset-name { + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vibe-asset-status { + font-size: 9px; + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + padding: 2px 6px; + border-radius: 0; + border: 1px solid var(--border-default); + color: var(--text-tertiary); +} +.vibe-asset-status.approved { + border-color: rgba(74, 222, 128, 0.4); + color: #86efac; + background: rgba(74, 222, 128, 0.12); +} +.vibe-asset-prompt { + font-size: 10px; + color: var(--text-tertiary); + line-height: 1.4; + margin: 4px 2px 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vibe-asset-actions { + display: flex; + gap: 4px; + margin-top: 6px; +} +.vibe-asset-actions .vibe-btn { + flex: 1; + padding: 5px 6px; + font-size: 10px; +} +.vibe-asset-item[draggable="true"] { + cursor: grab; +} +.vibe-asset-item[draggable="true"]:active { + cursor: grabbing; +} + +/* Staging editor visual mode */ +body.staging-mode { + background: #000; +} +body.staging-mode .watermark-logo { + opacity: 0.08; +} +body.staging-mode #overlay-top { + background: rgba(8, 10, 14, 0.92); + border-bottom-color: rgba(255,255,255,0.08); +} +/* In builder mode, change left panel header to reflect context */ +body.staging-mode .panel-title::after { + content: " · Asset Builder"; + color: var(--accent-primary); + font-weight: 600; +} +/* Hide the shape dropdown in builder mode — inline bar replaces it */ +body.staging-mode .shape-dropdown-wrapper { + display: none; +} + +/* Bottom input bar pinned to panel bottom */ +.vibe-bar { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + background: rgba(255,255,255,0.02); + border-top: 1px solid rgba(255,255,255,0.06); +} +.vibe-bar.active { + background: rgba(139, 92, 246, 0.04); + border-top-color: rgba(139, 92, 246, 0.15); +} + +.vibe-icon { + display: none; +} + +.vibe-input { + flex: 1; + min-width: 0; + padding: 6px 10px; + font-size: 12px; + font-weight: 500; + font-family: var(--font-mono); + background: rgba(0,0,0,0.35); + color: var(--text-primary); + border: 1px solid var(--border-default); + border-radius: 0; + outline: none; + transition: border-color var(--transition-fast); +} +.vibe-input:focus { + border-color: var(--accent-primary); +} +.vibe-input::placeholder { + color: var(--text-tertiary); +} +.vibe-input:disabled { + opacity: 0.5; +} + +.vibe-mode-toggle { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + font-weight: 600; + color: var(--text-tertiary); + cursor: pointer; + user-select: none; + padding: 4px 6px; + border-radius: var(--radius-sm); + transition: all var(--transition-fast); +} +.vibe-mode-toggle:hover { + color: var(--text-secondary); + background: var(--surface-1); +} +.vibe-mode-toggle input[type="checkbox"] { + width: 14px; + height: 14px; + margin: 0; + cursor: pointer; +} + +.vibe-btn { + flex-shrink: 0; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.06em; + text-transform: uppercase; + border: 1px solid var(--border-default); + border-radius: 0; + cursor: pointer; + white-space: nowrap; + transition: all var(--transition-fast); + background: transparent; + color: var(--text-secondary); +} +.vibe-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.vibe-btn-primary { + background: transparent; + color: var(--accent-primary); + border: 1px solid var(--accent-primary); +} +.vibe-btn-primary:hover { + background: var(--accent-primary-subtle); + color: var(--accent-primary-hover); + border-color: var(--accent-primary-hover); + box-shadow: none; +} +.vibe-btn-stop { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border-default); +} +.vibe-btn-stop:hover { + background: rgba(239,68,68,0.08); + border-color: #ef4444; + color: #f87171; +} + +.vibe-status { + flex-shrink: 0; + font-size: 11px; + font-weight: 500; + color: var(--text-tertiary); + white-space: nowrap; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; +} +.vibe-status:empty { + display: none; +} + +/* ===== Vibe Creator – Task Tracker (in-panel) ===== */ +.vibe-tracker { + flex: 1; + overflow-y: auto; + padding: 6px 0; + font-size: 12px; + scrollbar-width: thin; +} +.vibe-stream-details { + margin: 8px 10px 2px; + border: 1px solid var(--border-subtle); + border-radius: 0; + background: rgba(255,255,255,0.02); + overflow: hidden; +} +.vibe-stream-summary { + cursor: pointer; + padding: 8px 10px; + font-size: 11px; + color: var(--text-secondary); + border-bottom: 1px solid rgba(255,255,255,0.05); +} +.vibe-stream-body { + margin: 0; + padding: 10px; + max-height: 160px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; + font-size: 10px; + line-height: 1.45; + color: #cdd6f4; + background: rgba(8, 10, 14, 0.7); +} +.vibe-tracker::-webkit-scrollbar { + width: 4px; +} +.vibe-tracker::-webkit-scrollbar-thumb { + background: var(--border-default); + border-radius: 2px; +} + +/* Tracker header */ +.vt-header { + padding: 6px 14px 8px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-primary); + border-bottom: 1px solid var(--border-subtle); + display: flex; + align-items: center; + gap: 8px; +} +.vt-count { + font-size: 10px; + font-weight: 500; + font-family: var(--font-mono); + color: var(--text-tertiary); + background: transparent; + border: 1px solid var(--border-default); + padding: 0 6px; + border-radius: 0; +} + +/* Task items */ +.vt-task { + border-top: 1px solid transparent; +} +.vt-task + .vt-task { + border-top-color: var(--border-subtle); +} + +.vt-task-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 14px; + min-height: 28px; +} + +.vt-icon { + flex-shrink: 0; + width: 16px; + text-align: center; + font-size: 11px; + line-height: 1; +} + +/* Status-specific icon colors */ +.vt-pending .vt-icon { + color: var(--text-tertiary); +} +.vt-active .vt-icon { + color: #8B5CF6; + animation: vt-pulse 1.2s ease-in-out infinite; +} +.vt-done .vt-icon { + color: #4ade80; +} +.vt-failed .vt-icon { + color: #ef4444; +} + +@keyframes vt-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.vt-title { + flex: 1; + font-weight: 500; + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vt-active .vt-title { + color: var(--text-primary); + font-weight: 600; +} +.vt-done .vt-title { + color: var(--text-secondary); +} +.vt-pending .vt-title { + color: var(--text-tertiary); +} + +.vt-meta { + flex-shrink: 0; + font-size: 10px; + font-weight: 500; + color: var(--text-tertiary); + white-space: nowrap; +} +.vt-active .vt-meta { + color: #a78bfa; +} + +/* Task detail sub-entries */ +.vt-details { + padding: 0 14px 4px 38px; +} +.vt-detail { + font-size: 11px; + color: var(--text-tertiary); + line-height: 1.6; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vt-detail::before { + content: "› "; + color: var(--border-strong); +} + +/* Final polish note */ +.vt-final { + padding: 6px 14px; + font-size: 11px; + color: var(--text-secondary); + border-top: 1px solid var(--border-subtle); + display: flex; + align-items: baseline; + gap: 6px; +} +.vt-final-icon { + color: #fbbf24; + font-size: 12px; +} + +/* Vibe Creator – Empty state (shown when no generation is active) */ +.vibe-empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + gap: 12px; + text-align: center; + height: 100%; + min-height: 200px; +} +.vibe-empty-icon { + font-size: 32px; + opacity: 0.25; + user-select: none; +} +.vibe-empty-text { + font-size: 12px; + color: var(--text-tertiary); + max-width: 240px; + line-height: 1.6; +} + +/* Vibe Creator – Activity bar (what the agent is doing right now) */ +.vt-activity { + padding: 8px 14px; + font-size: 11px; + font-weight: 500; + color: #a78bfa; + background: rgba(139, 92, 246, 0.06); + border-top: 1px solid rgba(139, 92, 246, 0.15); + line-height: 1.5; + word-wrap: break-word; + white-space: normal; + animation: vt-pulse 1.2s ease-in-out infinite; +} +.vt-activity.hidden { + display: none; +} + +/* Agent Vision */ +.agent-vision { + border-bottom: 1px solid var(--border-subtle); +} + +.agent-shot-img { + width: 100%; + height: auto; + display: block; + background: var(--bg-primary); + aspect-ratio: 2.2 / 1; + object-fit: cover; +} + +.agent-decision-content { + margin: 0; + padding: 10px 12px; + font-size: 11px; + line-height: 1.5; + color: var(--text-primary); + white-space: pre-wrap; + word-break: break-word; + font-family: var(--font-mono); + background: var(--surface-1); + border-radius: 0; + border: 1px solid var(--border-subtle); + max-height: 160px; + overflow-y: auto; +} + +/* Collapsible Sections */ +.agent-collapse { + border-bottom: 1px solid var(--border-subtle); +} + +.agent-collapse:last-child { + border-bottom: none; +} + +.agent-collapse > summary { + cursor: pointer; + padding: 9px 12px; + font-size: 11px; + color: var(--text-secondary); + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.1em; + text-transform: uppercase; + user-select: none; + transition: all var(--transition-fast); + display: flex; + align-items: center; + gap: 8px; + list-style: none; +} + +.agent-collapse > summary::-webkit-details-marker { + display: none; +} + +.agent-collapse > summary::before { + content: "▸"; + font-size: 10px; + transition: transform var(--transition-fast); +} + +.agent-collapse[open] > summary::before { + transform: rotate(90deg); +} + +.agent-collapse > summary:hover { + background: var(--surface-1); + color: var(--text-primary); +} + +.agent-collapse-content { + padding: 0 12px 12px; +} + +.agent-observation-panel { + margin: 16px 12px 16px; + padding: 10px; + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + background: var(--surface-1); +} + +.agent-observation-panel .section-label { + margin-bottom: 8px; +} + +.agent-meta { + font-size: 11px; + color: var(--text-tertiary); + margin-bottom: 8px; + line-height: 1.5; +} + +/* Sub-collapse (nested details) */ +.agent-sub-collapse { + border: 1px solid var(--border-subtle); + border-radius: 0; + margin-bottom: 6px; + background: var(--surface-1); +} + +.agent-sub-collapse:last-child { + margin-bottom: 0; +} + +.agent-sub-collapse > summary { + cursor: pointer; + padding: 7px 10px; + font-size: 10px; + color: var(--text-tertiary); + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.08em; + text-transform: uppercase; + user-select: none; + transition: all var(--transition-fast); + display: flex; + align-items: center; + gap: 6px; + list-style: none; +} + +.agent-sub-collapse > summary::-webkit-details-marker { + display: none; +} + +.agent-sub-collapse > summary::before { + content: "▸"; + font-size: 9px; + transition: transform var(--transition-fast); +} + +.agent-sub-collapse[open] > summary::before { + transform: rotate(90deg); +} + +.agent-sub-collapse > summary:hover { + color: var(--text-secondary); +} + +.agent-pre { + margin: 0; + padding: 10px; + font-size: 10px; + line-height: 1.5; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; + font-family: var(--font-mono); + background: var(--bg-primary); + max-height: 300px; + overflow-y: auto; + border-top: 1px solid var(--border-subtle); +} + +/* Activity Log */ +.agent-log { + padding: 8px 12px 12px; + display: flex; + flex-direction: column; + gap: 4px; + max-height: 200px; + overflow-y: auto; +} + +.agent-log-item { + padding: 8px 10px; + border-radius: 0; + background: var(--surface-1); + border-left: 2px solid var(--border-default); + font-size: 10px; + font-family: var(--font-mono); + color: var(--text-secondary); + white-space: pre-wrap; + line-height: 1.4; +} + +/* ============================================================================ + Crosshair + ============================================================================ */ + +#crosshair { + position: fixed; + left: 50%; + top: 50%; + width: 20px; + height: 20px; + transform: translate(-50%, -50%); + pointer-events: none; + opacity: 0.9; +} + +#crosshair::before, +#crosshair::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + background: rgba(255, 255, 255, 0.9); + transform: translate(-50%, -50%); + border-radius: 1px; + transition: all var(--transition-fast); +} + +#crosshair::before { + width: 12px; + height: 2px; +} + +#crosshair::after { + width: 2px; + height: 12px; +} + +#crosshair.interactable::before, +#crosshair.interactable::after { + background: var(--accent-primary); + box-shadow: 0 0 10px var(--accent-primary-glow); +} + +#crosshair.interactable { + opacity: 1; +} + +#crosshair.holding::before, +#crosshair.holding::after { + background: var(--accent-warning); + box-shadow: 0 0 10px rgba(245, 158, 11, 0.5); +} + +#crosshair.holding { + opacity: 1; +} + +/* ============================================================================ + Interaction Hint + ============================================================================ */ + +#interaction-hint { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) translateY(40px); + pointer-events: none; + opacity: 0; + transition: all var(--transition-normal); + z-index: 101; + background: var(--bg-elevated); + border: 1px solid var(--accent-primary); + border-radius: 0; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + font-family: var(--font-mono); + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-primary); + white-space: nowrap; + backdrop-filter: blur(12px); + box-shadow: none; +} + +#interaction-hint.visible { + opacity: 1; + transform: translate(-50%, -50%) translateY(35px); +} + +#interaction-hint .hint-key { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + background: transparent; + border: 1px solid var(--accent-primary); + border-radius: 0; + padding: 0 6px; + margin-left: 8px; + font-size: 10px; + font-weight: 500; + font-family: var(--font-mono); + color: var(--accent-primary); + box-shadow: none; +} + +/* ============================================================================ + Interaction Popup + ============================================================================ */ + +#interaction-popup { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) translateY(70px); + background: var(--bg-elevated); + border: 1px solid var(--border-default); + border-radius: 0; + padding: 6px; + z-index: 150; + min-width: 220px; + backdrop-filter: blur(20px); + box-shadow: none; + animation: popupSlideIn 0.2s ease; + flex-direction: column; + gap: 4px; + pointer-events: auto; +} + +@keyframes popupSlideIn { + from { + opacity: 0; + transform: translate(-50%, -50%) translateY(60px) scale(0.95); + } + to { + opacity: 1; + transform: translate(-50%, -50%) translateY(70px) scale(1); + } +} + +.interact-action-btn { + width: 100%; + padding: 10px 14px !important; + text-align: left !important; + justify-content: flex-start !important; + margin-bottom: 4px; + background: var(--surface-1) !important; + border: 1px solid var(--border-subtle) !important; +} + +.interact-action-btn:last-child { + margin-bottom: 0; +} + +.interact-action-btn:hover { + background: var(--accent-primary-subtle) !important; + border-color: rgba(99, 102, 241, 0.3) !important; +} + +/* ============================================================================ + Visibility Utilities + ============================================================================ */ + +.hidden { + display: none !important; +} + +.edit-only, +.sim-only { + /* default shown */ +} + +html[data-mode="sim"] .edit-only { + display: none !important; +} + +html[data-mode="edit"] .sim-only { + display: none !important; +} + +/* Keep the agent command bar available in edit mode for iterative scene edits. */ +html[data-mode="edit"] #agent-command-bar.sim-only { + display: flex !important; +} + +/* In edit mode, keep Agent Vision/Response visible as a floating inspector. */ +html[data-mode="edit"] #agent-panel.sim-only { + display: flex !important; + position: fixed; + right: 16px; + bottom: 72px; + width: 360px; + max-height: 56vh; + z-index: 145; + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + backdrop-filter: blur(20px); + background: var(--bg-elevated); +} + +/* ============================================================================ + Responsive Design + ============================================================================ */ + +@media (max-width: 1100px) { + #overlay { + grid-template-columns: 280px 1fr 320px; + } + .tb-btn span { display: none; } /* hide text labels, keep icons */ + .tb-btn svg + span { display: none; } +} + +@media (max-width: 900px) { + #overlay { + grid-template-columns: 0 1fr 320px; + } + .side-panel-left { display: none; } + .left-panel-open { display: none !important; } +} + +@media (max-width: 640px) { + #overlay { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + grid-template-areas: "top" "."; + } + .side-panel { + height: auto; + max-height: 50vh; + } + .side-panel-left, .side-panel-right { display: none; } +} + +/* ============================================================================ + Scrollbar Styling + ============================================================================ */ + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--surface-3); + border-radius: 0; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.15); +} + +/* ============================================================================ + Selection + ============================================================================ */ + +::selection { + background: var(--accent-primary-subtle); + color: var(--text-primary); +} + +/* ============================================================================ + Level Editor – Shape Dropdown, Primitives, Lights + ============================================================================ */ + +/* Shape Dropdown */ +.shape-dropdown-wrapper { + position: relative; + display: inline-block; +} + +.shape-dropdown-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + z-index: 200; + background: var(--bg-secondary); + border: 1px solid var(--border-default); + border-radius: 0; + box-shadow: none; + padding: 4px; + min-width: 140px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.shape-dropdown-menu.hidden { + display: none; +} + +.shape-dropdown-menu button { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 12px; + background: transparent; + border: none; + color: var(--text-primary); + font-size: 13px; + font-family: inherit; + font-weight: 500; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition-fast); + text-align: left; +} + +.shape-dropdown-menu button:hover { + background: var(--surface-2); +} + +.shape-icon { + display: inline-flex; + width: 18px; + justify-content: center; + font-size: 14px; + opacity: 0.7; +} + +/* Primitive & Light Properties Panel */ +.prim-props { + padding: 0; +} + +.prim-props.hidden { + display: none; +} + +.prop-group { + margin-bottom: 6px; +} + +.prop-group-title { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-tertiary); + margin-bottom: 4px; +} + +.prop-row, .dt-row { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; +} + +.prop-label { + font-size: 11px; + font-weight: 600; + color: var(--text-tertiary); + min-width: 48px; + flex-shrink: 0; +} + +input[type="color"] { + -webkit-appearance: none; + appearance: none; + width: 32px; + height: 24px; + border: 1px solid var(--border-default); + border-radius: 0; + background: transparent; + cursor: pointer; + padding: 2px; +} + +input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } +input[type="color"]::-webkit-color-swatch { border: none; border-radius: 0; } + +.prop-check { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-secondary); + cursor: pointer; + padding: 4px 0; +} + +.prop-check input[type="checkbox"] { + accent-color: var(--accent-primary); + width: 13px; + height: 13px; +} + +/* Blob shadow sub-controls */ +.blob-shadow-controls { + padding: 4px 0 4px 19px; /* indent under the checkbox */ + display: flex; + flex-direction: column; + gap: 3px; +} +.blob-shadow-controls.hidden { display: none; } +.blob-shadow-controls .prop-row { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-secondary); +} +.blob-shadow-controls .prop-row label { + min-width: 52px; + flex-shrink: 0; +} +.blob-shadow-controls .prop-row input[type="range"] { + flex: 1; + height: 3px; + accent-color: var(--accent-primary); +} +.blob-shadow-controls .prop-row span { + min-width: 28px; + text-align: right; + font-variant-numeric: tabular-nums; +} +.blob-shadow-controls .prop-row input[type="number"] { + width: 60px; +} + +.prop-row-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 6px; + margin-bottom: 8px; +} + +.prop-input-group { + display: flex; + flex-direction: column; + gap: 2px; +} + +.prop-input-group label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + color: var(--text-tertiary); +} + +.prop-input-group input[type="number"] { + width: 100%; + padding: 6px 8px; + background: var(--surface-1); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 12px; + -moz-appearance: textfield; +} + +.prop-input-group input[type="number"]::-webkit-outer-spin-button, +.prop-input-group input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.prop-input-group input[type="number"]:focus { + outline: none; + border-color: var(--accent-primary); +} + +.file-sm { + font-size: 12px !important; + padding: 4px 10px !important; +} + +.btn-sm { + font-size: 12px; + padding: 4px 10px; + background: var(--surface-2); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + color: var(--text-secondary); + cursor: pointer; + font-family: inherit; + transition: all var(--transition-fast); +} + +.btn-sm:hover { + background: var(--surface-3); + color: var(--text-primary); +} + +.btn-sm.danger { + color: #ef4444; +} + +.btn-sm.danger:hover { + background: rgba(239, 68, 68, 0.15); + color: #f87171; +} + +/* Primitive textarea */ +.prim-props textarea { + width: 100%; + padding: 8px 10px; + background: var(--surface-1); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 13px; + margin-bottom: 8px; + resize: vertical; + min-height: 40px; + transition: border-color var(--transition-fast); +} + +.prim-props textarea:focus { + outline: none; + border-color: var(--accent-primary); +} + +/* Metadata Key-Value Editor */ +.meta-kv-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.meta-kv-empty { + font-size: 11px; + color: var(--text-tertiary); + font-style: italic; + padding: 4px 0; +} + +.meta-kv-row { + display: flex; + gap: 4px; + align-items: center; +} + +.meta-kv-key, +.meta-kv-val { + flex: 1; + min-width: 0; + padding: 5px 8px; + background: var(--surface-1); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 12px; + transition: border-color var(--transition-fast); +} + +.meta-kv-key { + flex: 0.45; + font-weight: 600; + color: var(--accent-info); +} + +.meta-kv-val { + flex: 0.55; +} + +.meta-kv-key:focus, +.meta-kv-val:focus { + outline: none; + border-color: var(--accent-primary); +} + +/* ============================================================================ + Agent Sensor Panels (sidebar) + ============================================================================ */ +.agent-sensor-label { + font-size: 10px; + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 8px 2px; +} + +.agent-sensor-canvas { + width: 100%; + height: auto; + display: block; + background: #000; + aspect-ratio: 2.2 / 1; +} diff --git a/misc/DimSim/update-sims.sh b/misc/DimSim/update-sims.sh new file mode 100644 index 0000000000..a672030781 --- /dev/null +++ b/misc/DimSim/update-sims.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Regenerate public/sims/manifest.json from the .json files in public/sims/. +# Run after adding or removing scene files. +set -e +DIR="$(cd "$(dirname "$0")" && pwd)/public/sims" +cd "$DIR" +echo -n "[" > manifest.json +first=true +for f in *.json; do + [ "$f" = "manifest.json" ] && continue + name="${f%.json}" + if [ "$first" = true ]; then first=false; else echo -n "," >> manifest.json; fi + echo -n "\"$name\"" >> manifest.json +done +echo "]" >> manifest.json +echo "Updated manifest: $(cat manifest.json)" diff --git a/misc/DimSim/vite.config.js b/misc/DimSim/vite.config.js new file mode 100644 index 0000000000..4d21fd3a4b --- /dev/null +++ b/misc/DimSim/vite.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + optimizeDeps: { + exclude: ["@sparkjsdev/spark", "@dimforge/rapier3d-compat"], + }, + assetsInclude: ["**/*.wasm"], + build: { + assetsInlineLimit: 0, + rollupOptions: { + external: [/^https:\/\/esm\.sh\//], + }, + }, + server: { + proxy: { + "/vlm": { + target: "http://127.0.0.1:8000", + changeOrigin: true, + }, + }, + }, +}); diff --git a/misc/DimSim/vlm-server/asset-library.json b/misc/DimSim/vlm-server/asset-library.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/misc/DimSim/vlm-server/asset-library.json @@ -0,0 +1 @@ +[] diff --git a/pyproject.toml b/pyproject.toml index 47b8a25f62..8f7fcccf6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -568,4 +568,7 @@ ignore = [ "dimos/manipulation/manipulation_module.py", "dimos/navigation/nav_stack/modules/*/main.cpp", "dimos/navigation/nav_stack/common/*.hpp", + "misc/DimSim/src/AiAvatar.js", + "misc/DimSim/src/style.css", + "misc/DimSim/src/engine.js", ] From 8492bacacda139bd5076c7a6d96db4bcfa569223 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Thu, 14 May 2026 08:06:27 +0300 Subject: [PATCH 02/43] skip lfs --- .pre-commit-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cb8099080..7591ab08f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,11 +39,13 @@ repos: args: [--fix=lf] exclude: \.patch$ # keep patch files byte-identical to upstream diffs - id: check-json + exclude: ^misc/DimSim/public/sims/ # LFS-tracked - id: check-toml - id: check-yaml - id: pretty-format-json name: format json args: [ --autofix, --no-sort-keys ] + exclude: ^misc/DimSim/public/sims/ # LFS-tracked - repo: https://github.com/editorconfig-checker/editorconfig-checker.python rev: 3.4.1 From e762c6570f4cf71d4f02d9cd573b0686e94d2a67 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 13:51:29 -0700 Subject: [PATCH 03/43] feat(dimsim): DIMSIM_LOCAL env, headless toggle, apartment as default scene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DIMSIM_LOCAL=1 (or path) makes DimSim run from a local checkout instead of cloning into ~/.local/state — handy when iterating on DimSim itself alongside dimos. - dimsim_headless flag in GlobalConfig (default True). When False, skip the Playwright install, drop --headless, and tell the user to open the URL manually. - Default dimsim_scene "apt" → "apartment" to match the renamed scene directory in DimSim. --- dimos/core/global_config.py | 3 ++- dimos/simulation/dimsim/dimsim_process.py | 30 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/dimos/core/global_config.py b/dimos/core/global_config.py index efba609849..28dedee746 100644 --- a/dimos/core/global_config.py +++ b/dimos/core/global_config.py @@ -65,8 +65,9 @@ class GlobalConfig(BaseSettings): obstacle_avoidance: bool = True detection_model: VlModelName = "moondream" listen_host: str = "127.0.0.1" - dimsim_scene: str = "apt" + dimsim_scene: str = "apartment" dimsim_port: int = 8090 + dimsim_headless: bool = True model_config = SettingsConfigDict( env_file=".env", diff --git a/dimos/simulation/dimsim/dimsim_process.py b/dimos/simulation/dimsim/dimsim_process.py index ead774c677..5c2e9d848c 100644 --- a/dimos/simulation/dimsim/dimsim_process.py +++ b/dimos/simulation/dimsim/dimsim_process.py @@ -44,8 +44,10 @@ def start(self) -> None: scene = self.global_config.dimsim_scene port = self.global_config.dimsim_port + headless = self.global_config.dimsim_headless - ensure_playwright_chromium(deno_path) + if headless: + ensure_playwright_chromium(deno_path) _kill_port_holder(port) render = os.environ.get("DIMSIM_RENDER", "gpu").strip() @@ -60,7 +62,7 @@ def start(self) -> None: "--port", str(port), "--no-depth", - "--headless", + *(("--headless",) if headless else ()), "--render", render, "--image-rate", @@ -69,6 +71,11 @@ def start(self) -> None: str(_LIDAR_RATE), ] + if not headless: + logger.info( + f"Open http://localhost:{port} in your browser; sensors won't publish until that tab is loaded." + ) + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self._start_log_reader() @@ -127,6 +134,25 @@ def _kill_port_holder(port: int) -> None: def _ensure_repo() -> Path: + # DIMSIM_LOCAL — point at a local DimSim checkout instead of cloning. + # DIMSIM_LOCAL=1 → ../DimSim sibling of the dimos repo + # DIMSIM_LOCAL=/some/path → that path + local = os.environ.get("DIMSIM_LOCAL", "").strip() + if local: + if local == "1": + # Walk up from this file to the dimos repo root, then to sibling DimSim + dimos_root = Path(__file__).resolve().parents[3] + path = dimos_root.parent / "DimSim" + else: + path = Path(local).expanduser().resolve() + if not (path / "dimos-cli" / "cli.ts").exists(): + raise RuntimeError( + f"DIMSIM_LOCAL={local} resolved to {path}, but " + f"{path}/dimos-cli/cli.ts does not exist" + ) + logger.info(f"Using local DimSim from {path}") + return path + repo_dir = STATE_DIR / "dimsim_repo" if (repo_dir / ".git").exists(): return repo_dir From 8d5661a2348119d9ad8abadb42e0e5e5b5d8a5d7 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 14:02:14 -0700 Subject: [PATCH 04/43] misc/DimSim: sync from standalone (apartment decompose, loadLevel) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces Paul's vendored snapshot with current standalone DimSim. Brings in recent work that wasn't yet in his vendor: - scenes/apartment/ now uses JS-authored data modules under data/* plus extracted texture files under textures/. No more 97MB apt.json. loadLevel() in sceneApi.ts feeds the apt-shape blob to importLevelFromJSON, so E-key interactivity (pickup, multi-state cabinets, TV) works exactly as before. - scripts/extract_apt_to_js.py — one-shot decomposer used to produce the data/ modules above. - bridge/, src/, scene-api updates — newer code than what Paul vendored. Preserved Paul's one functional adaptation: cli.ts.resolveDistDir() now includes the tryBuildFromSource() fallback, so on first run inside the dimos repo we materialize dist/ via Deno+Vite (dist/ is gitignored and not committed in this layout). Dropped Paul's redundant public/sims/apt.json snapshot — apartment data lives in scenes/apartment/data/ now. --- misc/DimSim/.gitignore | 1 + misc/DimSim/dimos-cli/agent.py | 31 +- misc/DimSim/dimos-cli/bridge/lidar.ts | 27 +- misc/DimSim/dimos-cli/bridge/physics.ts | 123 +- misc/DimSim/dimos-cli/bridge/server.ts | 208 +- misc/DimSim/dimos-cli/cli.ts | 11 +- misc/DimSim/dimos-cli/deno.json | 6 +- misc/DimSim/dimos-cli/setup.ts | 28 +- misc/DimSim/dimos-cli/test/add_godcam.py | 78 + .../dimos-cli/test/add_purple_object.py | 60 + .../DimSim/dimos-cli/test/diagnose_costmap.py | 72 +- .../dimos-cli/test/dimos_integration.py | 58 +- misc/DimSim/dimos-cli/test/lcm_cross_test.py | 21 +- misc/DimSim/dimos-cli/test/list_assets.py | 58 + misc/DimSim/dimos-cli/test/list_scene.py | 30 + .../dimos-cli/test/scene_editor_test.py | 41 +- misc/DimSim/docs/sdk-design.md | 253 + misc/DimSim/evals/apt/go-to-couch.json | 7 +- misc/DimSim/evals/apt/go-to-kitchen.json | 7 +- misc/DimSim/evals/apt/go-to-tv.json | 7 +- misc/DimSim/index.html | 6 +- misc/DimSim/public/sims/apt.json | 3 - misc/DimSim/public/sims/apt_.json | 4 +- misc/DimSim/public/sims/empty.json | 4 +- misc/DimSim/public/sims/manifest.json | 4 +- misc/DimSim/scenes/apartment/data/groups.js | 175 + misc/DimSim/scenes/apartment/data/lights.js | 219 + misc/DimSim/scenes/apartment/data/objects.js | 69168 ++++++++++++++++ misc/DimSim/scenes/apartment/data/sky.js | 11 + .../DimSim/scenes/apartment/data/structure.js | 5664 ++ misc/DimSim/scenes/apartment/data/tags.js | 7 + misc/DimSim/scenes/apartment/index.js | 31 + .../apartment/textures/048224345952.webp | Bin 0 -> 136276 bytes .../apartment/textures/08e5e2b877e1.jpg | 3 + .../apartment/textures/1c8ef077e400.jpg | 3 + .../apartment/textures/2518091b2c71.jpg | 3 + .../apartment/textures/2b666e22f0a6.jpg | 3 + .../apartment/textures/3885a68cb0cb.webp | Bin 0 -> 13220 bytes .../apartment/textures/4d5b048c925d.avif | Bin 0 -> 65914 bytes .../apartment/textures/84fdd8bbff5e.jpg | 3 + .../apartment/textures/9049fe942dc4.jpg | 3 + .../apartment/textures/a2ba694ad53e.jpg | 3 + .../apartment/textures/af07500a616c.webp | Bin 0 -> 14942 bytes .../apartment/textures/c83879b7f455.jpg | 3 + .../apartment/textures/d4679a510dcb.avif | Bin 0 -> 1631263 bytes .../apartment/textures/dc34e383b17a.jpg | 3 + .../apartment/textures/f29666a3db99.avif | Bin 0 -> 124147 bytes .../apartment/textures/faada43680d3.jpg | 3 + misc/DimSim/scenes/empty/index.js | 19 + misc/DimSim/scripts/apt_to_single_glb.py | 91 + misc/DimSim/scripts/decompose_apt.py | 454 + .../scripts/decompose_objects_to_glb.py | 300 + misc/DimSim/scripts/extract_apt_to_js.py | 181 + misc/DimSim/scripts/package-release.sh | 8 +- misc/DimSim/scripts/speed-test.py | 39 +- misc/DimSim/src/AiAvatar.js | 62 +- misc/DimSim/src/ai/sim/vlmPrompt.js | 2 +- misc/DimSim/src/ai/visionCapture.js | 12 +- misc/DimSim/src/ai/vlmClient.js | 2 + misc/DimSim/src/dimos/sceneApi.ts | 393 + misc/DimSim/src/dimos/sceneEditor.ts | 13 + misc/DimSim/src/engine.js | 386 +- misc/DimSim/src/style.css | 10 +- 63 files changed, 77978 insertions(+), 447 deletions(-) create mode 100644 misc/DimSim/dimos-cli/test/add_godcam.py create mode 100644 misc/DimSim/dimos-cli/test/add_purple_object.py create mode 100644 misc/DimSim/dimos-cli/test/list_assets.py create mode 100644 misc/DimSim/dimos-cli/test/list_scene.py create mode 100644 misc/DimSim/docs/sdk-design.md delete mode 100644 misc/DimSim/public/sims/apt.json create mode 100644 misc/DimSim/scenes/apartment/data/groups.js create mode 100644 misc/DimSim/scenes/apartment/data/lights.js create mode 100644 misc/DimSim/scenes/apartment/data/objects.js create mode 100644 misc/DimSim/scenes/apartment/data/sky.js create mode 100644 misc/DimSim/scenes/apartment/data/structure.js create mode 100644 misc/DimSim/scenes/apartment/data/tags.js create mode 100644 misc/DimSim/scenes/apartment/index.js create mode 100644 misc/DimSim/scenes/apartment/textures/048224345952.webp create mode 100644 misc/DimSim/scenes/apartment/textures/08e5e2b877e1.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/1c8ef077e400.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/2518091b2c71.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/2b666e22f0a6.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/3885a68cb0cb.webp create mode 100644 misc/DimSim/scenes/apartment/textures/4d5b048c925d.avif create mode 100644 misc/DimSim/scenes/apartment/textures/84fdd8bbff5e.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/9049fe942dc4.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/a2ba694ad53e.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/af07500a616c.webp create mode 100644 misc/DimSim/scenes/apartment/textures/c83879b7f455.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/d4679a510dcb.avif create mode 100644 misc/DimSim/scenes/apartment/textures/dc34e383b17a.jpg create mode 100644 misc/DimSim/scenes/apartment/textures/f29666a3db99.avif create mode 100644 misc/DimSim/scenes/apartment/textures/faada43680d3.jpg create mode 100644 misc/DimSim/scenes/empty/index.js create mode 100644 misc/DimSim/scripts/apt_to_single_glb.py create mode 100644 misc/DimSim/scripts/decompose_apt.py create mode 100644 misc/DimSim/scripts/decompose_objects_to_glb.py create mode 100644 misc/DimSim/scripts/extract_apt_to_js.py create mode 100644 misc/DimSim/src/dimos/sceneApi.ts diff --git a/misc/DimSim/.gitignore b/misc/DimSim/.gitignore index ce85a96daa..83ed1fc634 100644 --- a/misc/DimSim/.gitignore +++ b/misc/DimSim/.gitignore @@ -5,3 +5,4 @@ dist/ **/.DS_Store .deno/ scenes.json +scenes/**/apt.json diff --git a/misc/DimSim/dimos-cli/agent.py b/misc/DimSim/dimos-cli/agent.py index ea68949272..0c22f4543c 100644 --- a/misc/DimSim/dimos-cli/agent.py +++ b/misc/DimSim/dimos-cli/agent.py @@ -1,18 +1,4 @@ #!/usr/bin/env python3 -# Copyright 2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ DimSim Agent — runs the dimos nav + agent stack connected to DimSim via LCM. @@ -28,6 +14,7 @@ """ import argparse +import sys from dimos.core.blueprints import autoconnect from dimos.core.transport import JpegLcmTransport, LCMTransport @@ -48,15 +35,13 @@ } # Navigation stack: LiDAR → voxels → costmap → frontier explorer → path planner -nav = ( - autoconnect( - voxel_mapper(voxel_size=0.1), - cost_mapper(algo="simple"), - replanning_a_star_planner(), - wavefront_frontier_explorer(), - ) - .transports(_transports) - .global_config(n_dask_workers=6, robot_model="dimsim") +nav = autoconnect( + voxel_mapper(voxel_size=0.1), + cost_mapper(algo="simple"), + replanning_a_star_planner(), + wavefront_frontier_explorer(), +).transports(_transports).global_config( + n_dask_workers=6, robot_model="dimsim" ) diff --git a/misc/DimSim/dimos-cli/bridge/lidar.ts b/misc/DimSim/dimos-cli/bridge/lidar.ts index 275a0af1f5..c2e451c6cd 100644 --- a/misc/DimSim/dimos-cli/bridge/lidar.ts +++ b/misc/DimSim/dimos-cli/bridge/lidar.ts @@ -18,7 +18,11 @@ const MIN_RANGE = 0.1; const MAX_RANGE = 4; const V_MIN_RAD = (-30 * Math.PI) / 180; const V_MAX_RAD = (15 * Math.PI) / 180; -const RATE_MS = 100; // 10 Hz +// 5 Hz — at 15k raycasts per scan, 10 Hz starves the physics setInterval +// (each scan takes 25-30ms on M4, ~60-80ms on weaker runners). 5 Hz halves +// the per-second blocking budget and matches typical low-end LiDAR rates +// (RPLIDAR, low-Velodyne). Module docstring already advertised 5 Hz. +const RATE_MS = 200; // 5 Hz const CH_LIDAR = "/lidar#sensor_msgs.PointCloud2"; @@ -87,6 +91,9 @@ function rotateByQuat( // -- ServerLidar -------------------------------------------------------------- export class ServerLidar { + // Resolved once at module load; hot-path callers should not Deno.env.get every scan. + static readonly PROFILE = Deno.env.get("DIMSIM_PROFILE_PHYSICS") === "1"; + private lcm: LCM; private world: any; // RAPIER.World private RAPIER: any; @@ -176,6 +183,8 @@ export class ServerLidar { } private async _doScan(): Promise { + const profile = ServerLidar.PROFILE; + const scanStart = profile ? performance.now() : 0; this.scanCount++; const jitterAngle = this.scanCount * 2.399963; // golden angle per scan const cosJ = Math.cos(jitterAngle); @@ -192,6 +201,8 @@ export class ServerLidar { const ox = this.px, oy = this.py, oz = this.pz; const rqx = this.qx, rqy = this.qy, rqz = this.qz, rqw = this.qw; + const raycastStart = profile ? performance.now() : 0; + for (let i = 0; i < NUM_POINTS; i++) { // Fibonacci direction (pre-rotated to camera-local) with per-scan golden angle jitter. // In FLU frame, jitter rotates around Z (up). After lidarToCamQuat, FLU Z → cam Y, @@ -230,6 +241,8 @@ export class ServerLidar { n++; } + const raycastEnd = profile ? performance.now() : 0; + if (n === 0) return; this.logN++; @@ -280,5 +293,17 @@ export class ServerLidar { this.sentSeqs.add(this.lcm.getNextSeq()); // Publish directly to LCM — no WS hop (await so buffer pressure is felt) await this.lcm.publish(CH_LIDAR, msg); + + if (profile) { + const total = performance.now() - scanStart; + const raycast = raycastEnd - raycastStart; + // Log every scan — there are only 10 per second. + if (this.scanCount % 5 === 0) { + console.log( + `[lidar-prof] scan=${this.scanCount} raycast=${raycast.toFixed(1)}ms ` + + `total=${total.toFixed(1)}ms hits=${n}/${NUM_POINTS}` + ); + } + } } } diff --git a/misc/DimSim/dimos-cli/bridge/physics.ts b/misc/DimSim/dimos-cli/bridge/physics.ts index e902de3fc9..d2bcf67f34 100644 --- a/misc/DimSim/dimos-cli/bridge/physics.ts +++ b/misc/DimSim/dimos-cli/bridge/physics.ts @@ -80,6 +80,18 @@ export class ServerPhysics { private yaw = 0; private seq = 0; + // Profiling (DIMSIM_PROFILE_PHYSICS=1) — rolling timing per phase. + private profile = false; + private lastStepStart = 0; + private prof = { + n: 0, + sumCompute: 0, maxCompute: 0, + sumStep: 0, maxStep: 0, + sumPublish: 0, maxPublish: 0, + sumTotal: 0, maxTotal: 0, + sumInterval: 0, maxInterval: 0, + }; + // cmd_vel (ROS frame: x=fwd, z=yaw) private linX = 0; // forward private linY = 0; // lateral @@ -90,6 +102,8 @@ export class ServerPhysics { // Callback to send position to browser private onPoseUpdate: ((x: number, y: number, z: number, yaw: number) => void) | null = null; + private userColliders = new Map(); + constructor( lcm: LCM, rapierWorld: any, @@ -121,6 +135,11 @@ export class ServerPhysics { let colliderCount = 0; this.world.colliders.forEach(() => { colliderCount++; }); // Quiet init — only log on error or reconfigure + + this.profile = Deno.env.get("DIMSIM_PROFILE_PHYSICS") === "1"; + if (this.profile) { + console.log(`[physics-prof] enabled — colliderCount=${colliderCount} target=${PHYSICS_HZ}Hz (${(1000 / PHYSICS_HZ).toFixed(1)}ms interval)`); + } } private _createBodyAndColliders(): void { @@ -210,6 +229,63 @@ export class ServerPhysics { // quiet } + /** Add a user-authored collider to the world. The browser sends these via + * `physicsColliderAdd` when scenes are edited live (SceneClient.add_object, + * load_map, etc.). Without this the agent has no floor to stand on. */ + addCollider(uuid: string, desc: any): void { + if (!desc || this.userColliders.has(uuid)) return; + const RAPIER = this.RAPIER; + const clamp = (v: number) => Math.max(0.001, v); + let cd: any; + if (desc.shape === "sphere" && desc.radius != null) { + cd = RAPIER.ColliderDesc.ball(clamp(desc.radius)); + } else if (desc.shape === "trimesh" && desc.vertices && desc.indices) { + cd = RAPIER.ColliderDesc.trimesh( + new Float32Array(desc.vertices), + new Uint32Array(desc.indices), + ); + } else if (desc.halfExtents) { + const h = desc.halfExtents; + cd = RAPIER.ColliderDesc.cuboid(clamp(h.x), clamp(h.y), clamp(h.z)); + } else { + return; + } + cd.setFriction(0.9); + if (desc.restitution != null) cd.setRestitution(desc.restitution); + const pos = desc.position ?? { x: 0, y: 0, z: 0 }; + if (desc.dynamic) { + const body = this.world.createRigidBody( + RAPIER.RigidBodyDesc.dynamic().setTranslation(pos.x, pos.y, pos.z), + ); + if (desc.mass != null) body.setAdditionalMass(desc.mass); + const collider = this.world.createCollider(cd, body); + this.userColliders.set(uuid, { collider, body }); + } else { + cd.setTranslation(pos.x, pos.y, pos.z); + const collider = this.world.createCollider(cd); + this.userColliders.set(uuid, { collider, body: null }); + } + } + + removeCollider(uuid: string): void { + const entry = this.userColliders.get(uuid); + if (!entry) return; + if (entry.body) { + this.world.removeRigidBody(entry.body); + } else if (entry.collider) { + this.world.removeCollider(entry.collider, false); + } + this.userColliders.delete(uuid); + } + + /** Drop every user-authored collider. Used when hot-reloading a scene + * from JSON — the new content's colliders get re-added afterwards. */ + clearUserColliders(): void { + for (const uuid of [...this.userColliders.keys()]) { + this.removeCollider(uuid); + } + } + /** Set callback for browser position sync. */ setOnPoseUpdate( cb: (x: number, y: number, z: number, yaw: number) => void, @@ -264,6 +340,14 @@ export class ServerPhysics { } private _step(): void { + const stepStart = this.profile ? performance.now() : 0; + if (this.profile && this.lastStepStart) { + const interval = stepStart - this.lastStepStart; + this.prof.sumInterval += interval; + if (interval > this.prof.maxInterval) this.prof.maxInterval = interval; + } + this.lastStepStart = stepStart; + // Safety timeout — zero velocity if no cmd_vel received recently const hasVel = Date.now() - this.cmdVelStamp < CMD_VEL_TIMEOUT_MS; const linX = hasVel ? this.linX * this.speedScale : 0; @@ -327,19 +411,52 @@ export class ServerPhysics { this.body.setNextKinematicTranslation(newPos); + const computeEnd = this.profile ? performance.now() : 0; + // Step world to apply kinematic translation (needed for next computeColliderMovement) this.world.step(); + const stepEnd = this.profile ? performance.now() : 0; + // Publish odom to LCM (Three.js Y-up → ROS Z-up) this._publishOdom(newPos); - // Debug: log first few steps - // step logging removed — too noisy for dimos subprocess output - // Notify browser for visual sync if (this.onPoseUpdate) { this.onPoseUpdate(newPos.x, newPos.y, newPos.z, this.yaw); } + + if (this.profile) { + const publishEnd = performance.now(); + const compute = computeEnd - stepStart; + const step = stepEnd - computeEnd; + const publish = publishEnd - stepEnd; + const total = publishEnd - stepStart; + const p = this.prof; + p.n++; + p.sumCompute += compute; if (compute > p.maxCompute) p.maxCompute = compute; + p.sumStep += step; if (step > p.maxStep) p.maxStep = step; + p.sumPublish += publish; if (publish > p.maxPublish) p.maxPublish = publish; + p.sumTotal += total; if (total > p.maxTotal) p.maxTotal = total; + + // Log rolling averages every 20 steps (~0.4s at 50Hz target, ~4s at 5Hz actual). + if (p.n >= 20) { + const intervalAvg = p.sumInterval / Math.max(p.n - 1, 1); + const effHz = intervalAvg > 0 ? 1000 / intervalAvg : 0; + console.log( + `[physics-prof] n=${p.n} ` + + `compute=${(p.sumCompute / p.n).toFixed(2)}/${p.maxCompute.toFixed(1)}ms ` + + `step=${(p.sumStep / p.n).toFixed(2)}/${p.maxStep.toFixed(1)}ms ` + + `pub=${(p.sumPublish / p.n).toFixed(2)}/${p.maxPublish.toFixed(1)}ms ` + + `total=${(p.sumTotal / p.n).toFixed(2)}/${p.maxTotal.toFixed(1)}ms ` + + `interval=${intervalAvg.toFixed(1)}/${p.maxInterval.toFixed(1)}ms ` + + `effHz=${effHz.toFixed(1)}` + ); + // reset rolling counters but keep maxima — useful to spot worst-case drift + p.n = 0; + p.sumCompute = p.sumStep = p.sumPublish = p.sumTotal = p.sumInterval = 0; + } + } } private _publishOdom(pos: { x: number; y: number; z: number }): void { diff --git a/misc/DimSim/dimos-cli/bridge/server.ts b/misc/DimSim/dimos-cli/bridge/server.ts index 6824a2ee55..16b67a8a74 100644 --- a/misc/DimSim/dimos-cli/bridge/server.ts +++ b/misc/DimSim/dimos-cli/bridge/server.ts @@ -25,6 +25,23 @@ const SNAPSHOT_MAGIC = 0x4453534E; const DEFAULT_LCM_PORT = 7667; const DEFAULT_LCM_HOST = "239.255.76.67"; +const SCENE_MIME: Record = { + js: "application/javascript; charset=utf-8", + mjs: "application/javascript; charset=utf-8", + json: "application/json; charset=utf-8", + glb: "model/gltf-binary", + gltf: "model/gltf+json", + bin: "application/octet-stream", + png: "image/png", + jpg: "image/jpeg", + jpeg: "image/jpeg", + webp: "image/webp", + avif: "image/avif", + ktx2: "image/ktx2", + hdr: "image/vnd.radiance", + exr: "image/x-exr", +}; + export interface BridgeServerOptions { port: number; distDir: string; @@ -59,6 +76,58 @@ export async function startBridgeServer(options: BridgeServerOptions) { sensorRates, sensorEnable, cameraFov, } = options; + // Scene the engine boots into. Injected as window.__dimosScene below so + // engine.js dynamically imports /scenes//index.js. + const activeSceneName: string = scene || "empty"; + + // Event-loop lag probe — fire setTimeout(0) every 50ms; difference between + // expected and actual fire time is contention-induced lag. If physics misses + // ticks, this will show why. + if (Deno.env.get("DIMSIM_PROFILE_PHYSICS") === "1") { + let lagSum = 0, lagMax = 0, lagCount = 0; + const tick = () => { + const expected = performance.now() + 50; + setTimeout(() => { + const lag = performance.now() - expected; + lagSum += Math.max(lag, 0); + if (lag > lagMax) lagMax = lag; + lagCount++; + if (lagCount >= 20) { // ~1s + console.log(`[loop-prof] lag avg=${(lagSum / lagCount).toFixed(1)}ms max=${lagMax.toFixed(1)}ms (over ${lagCount} probes)`); + lagSum = 0; lagMax = 0; lagCount = 0; + } + tick(); + }, 50); + }; + tick(); + } + + // ── Per-handler profiling -------------------------------------------------- + // Records sum/max/count for two hot callbacks so we can see whether they + // dominate the bridge thread alongside lidar. + const PROFILE = Deno.env.get("DIMSIM_PROFILE_PHYSICS") === "1"; + const profPose = { n: 0, sum: 0, max: 0 }; + const profRelay = { n: 0, sum: 0, max: 0, bytes: 0 }; + if (PROFILE) { + setInterval(() => { + if (profPose.n > 0) { + console.log( + `[pose-cb-prof] n=${profPose.n} avg=${(profPose.sum / profPose.n).toFixed(2)}ms ` + + `max=${profPose.max.toFixed(2)}ms total=${profPose.sum.toFixed(0)}ms/sec`, + ); + profPose.n = 0; profPose.sum = 0; profPose.max = 0; + } + if (profRelay.n > 0) { + console.log( + `[relay-prof] n=${profRelay.n} avg=${(profRelay.sum / profRelay.n).toFixed(2)}ms ` + + `max=${profRelay.max.toFixed(2)}ms total=${profRelay.sum.toFixed(0)}ms/sec ` + + `bytes=${profRelay.bytes}`, + ); + profRelay.n = 0; profRelay.sum = 0; profRelay.max = 0; profRelay.bytes = 0; + } + }, 1000); + } + // Build channel list: if channels provided, use them; otherwise single default const channelNames = channels && channels.length > 0 ? channels @@ -110,6 +179,44 @@ export async function startBridgeServer(options: BridgeServerOptions) { channelMap.set(name, state); } + (async () => { + const candidates = [ + Deno.env.get("DIMSIM_SCENES_DIR"), + `${distDir}/scenes`, + `${distDir}/../scenes`, + ].filter((d): d is string => !!d && d.length > 0); + let watchDir: string | null = null; + for (const d of candidates) { + try { + await Deno.stat(`${d}/${activeSceneName}`); + watchDir = `${d}/${activeSceneName}`; + break; + } catch { /* try next */ } + } + if (!watchDir) return; + console.log(`[bridge] hot-reload watching ${watchDir}`); + let last = 0; + try { + for await (const event of Deno.watchFs(watchDir)) { + if (event.kind !== "modify" && event.kind !== "create") continue; + const now = performance.now(); + if (now - last < 250) continue; + last = now; + const msg = JSON.stringify({ type: "reload" }); + for (const ch of channelMap.values()) { + for (const client of ch.controlClients) { + if (client.readyState === WebSocket.OPEN) { + try { client.send(msg); } catch { /* ignore */ } + } + } + } + console.log(`[bridge] hot-reload`); + } + } catch (e) { + console.warn(`[bridge] watcher failed: ${e}`); + } + })(); + /** Resolve channel from WS query param. Falls back to default ("") if not found. */ function resolveChannel(channelParam: string | null): ChannelState { if (channelParam && channelMap.has(channelParam)) { @@ -142,7 +249,8 @@ export async function startBridgeServer(options: BridgeServerOptions) { for (const handle of bodiesToRemove) { world.removeRigidBody(world.getRigidBody(handle)); } - console.log(`[bridge:${chState.name || "default"}] Rapier snapshot restored (removed ${bodiesToRemove.length} non-fixed bodies)`); + // Single canonical "physics live" marker — test fixtures grep for this. + console.log(`[bridge:${chState.name || "default"}] ready`); chState.serverPhysics = new ServerPhysics(chState.lcm, world, RAPIER, chState.sentSeqs, chState.embodiment ?? undefined); if (spawnPos) { @@ -153,6 +261,7 @@ export async function startBridgeServer(options: BridgeServerOptions) { chState.serverLidar.setExcludeBody(chState.serverPhysics.getBody()); chState.serverPhysics.setOnPoseUpdate((x, y, z, yaw) => { + const t0 = PROFILE ? performance.now() : 0; const qw = Math.cos(yaw / 2); const qy = Math.sin(yaw / 2); chState.serverLidar!.updatePose(x, y, z, 0, qy, 0, qw); @@ -162,6 +271,12 @@ export async function startBridgeServer(options: BridgeServerOptions) { if (client && client.readyState === WebSocket.OPEN) { try { client.send(msg); } catch { /* ignore */ } } + if (PROFILE) { + const dt = performance.now() - t0; + profPose.n++; + profPose.sum += dt; + if (dt > profPose.max) profPose.max = dt; + } }); chState.serverPhysics.start(); @@ -190,35 +305,74 @@ export async function startBridgeServer(options: BridgeServerOptions) { socket.onclose = () => { chState.sensorClients.delete(socket); }; socket.onerror = () => chState.sensorClients.delete(socket); + // Chunked snapshot reassembly state (DSC1 protocol). + // Browser ships the Rapier snapshot in many small frames so a + // CPU-starved main thread can drain the WebSocket pump. + let chunkedSnapshot: { + total: number; + spawn: { x: number; y: number; z: number }; + received: number; + parts: Uint8Array[]; + } | null = null; + let _sensorLogN = 0; socket.onmessage = (event: MessageEvent) => { if (!(event.data instanceof ArrayBuffer) || !chState.lcm) return; const packet = new Uint8Array(event.data); + // While reassembling a chunked snapshot, treat every binary frame + // on this socket as the next chunk in order. + if (chunkedSnapshot) { + chunkedSnapshot.parts.push(packet); + chunkedSnapshot.received += packet.byteLength; + if (chunkedSnapshot.received >= chunkedSnapshot.total) { + const combined = new Uint8Array(chunkedSnapshot.received); + let off = 0; + for (const p of chunkedSnapshot.parts) { combined.set(p, off); off += p.byteLength; } + const snapshot = combined.subarray(0, chunkedSnapshot.total); + const spawn = chunkedSnapshot.spawn; + chunkedSnapshot = null; + initServerSystems(chState, snapshot, spawn); + } + return; + } + // Check for Rapier snapshot if (packet.length > 4) { const dv = new DataView(packet.buffer, packet.byteOffset); const magic = dv.getUint32(0, false); + if (magic === 0x44534331) { // "DSC1" — chunked prelude + const total = dv.getUint32(4, true); + const sx = dv.getFloat32(8, true); + const sy = dv.getFloat32(12, true); + const sz = dv.getFloat32(16, true); + chunkedSnapshot = { + total, + spawn: { x: sx, y: sy, z: sz }, + received: 0, + parts: [], + }; + return; + } + if (magic === 0x44535332) { // "DSS2" const sx = dv.getFloat32(4, true); const sy = dv.getFloat32(8, true); const sz = dv.getFloat32(12, true); const snapshot = packet.slice(16); - const spawnPos = { x: sx, y: sy, z: sz }; - console.log(`${logPrefix} Rapier snapshot received (${(snapshot.byteLength / 1024).toFixed(0)}KB) spawn=(${sx.toFixed(1)},${sy.toFixed(1)},${sz.toFixed(1)})`); - initServerSystems(chState, snapshot, spawnPos); + initServerSystems(chState, snapshot, { x: sx, y: sy, z: sz }); return; } if (magic === SNAPSHOT_MAGIC) { // "DSSN" const snapshot = packet.slice(4); - console.log(`${logPrefix} Rapier snapshot received (${(snapshot.byteLength / 1024).toFixed(0)}KB) [legacy, no spawn]`); initServerSystems(chState, snapshot); return; } } + const t0 = PROFILE ? performance.now() : 0; try { const decoded = decodePacket(packet); if (decoded && decoded.type === "small") { @@ -231,6 +385,13 @@ export async function startBridgeServer(options: BridgeServerOptions) { chState.lcm.publishRaw(decoded.channel, decoded.data).catch(() => {}); } } catch { /* ignore */ } + if (PROFILE) { + const dt = performance.now() - t0; + profRelay.n++; + profRelay.sum += dt; + profRelay.bytes += packet.byteLength; + if (dt > profRelay.max) profRelay.max = dt; + } }; } else { // ── CONTROL WebSocket ───────────────────────────────────────── @@ -275,9 +436,15 @@ export async function startBridgeServer(options: BridgeServerOptions) { return; // don't relay teleport commands } - // -- Physics collider add/remove: forward to Rapier world -- - if (msg.type === "physicsColliderAdd" || msg.type === "physicsColliderRemove") { - // These are handled by the browser's physics; just relay + // -- Physics collider add/remove: also apply to ServerPhysics + // world so live-authored colliders (floor, walls, dynamic balls) + // become real obstacles for the server-side agent. Without this, + // the dog falls through anything added after the boot snapshot. + if (msg.type === "physicsColliderAdd" && msg.uuid && msg.desc) { + chState.serverPhysics?.addCollider(msg.uuid, msg.desc); + } + if (msg.type === "physicsColliderRemove" && msg.uuid) { + chState.serverPhysics?.removeCollider(msg.uuid); } } catch { /* not JSON, relay as-is */ } @@ -316,7 +483,7 @@ export async function startBridgeServer(options: BridgeServerOptions) { const ratesJs = sensorRates ? `window.__dimosSensorRates=${JSON.stringify(sensorRates)};` : ""; const enableJs = sensorEnable ? `window.__dimosSensorEnable=${JSON.stringify(sensorEnable)};` : ""; const fovJs = cameraFov ? `window.__dimosCameraFov=${cameraFov};` : ""; - const inject = ``; + const inject = ``; html = html.replace("", `${inject}\n`); return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } }); } catch { @@ -324,6 +491,29 @@ export async function startBridgeServer(options: BridgeServerOptions) { } } + // JS scene project folders. Resolution order: + // 1. DIMSIM_SCENES_DIR env (dimos points this at user-authored scenes) + // 2. dist/scenes/ (shipped built-ins, when running from a built binary) + // 3. ../scenes/ (dev built-ins, when running directly from source) + if (url.pathname.startsWith("/scenes/")) { + const rel = url.pathname.slice("/scenes/".length); + const candidates = [ + Deno.env.get("DIMSIM_SCENES_DIR"), + `${distDir}/scenes`, + `${distDir}/../scenes`, + ].filter((d): d is string => !!d && d.length > 0); + for (const dir of candidates) { + try { + const filePath = `${dir}/${rel}`; + const data = await Deno.readFile(filePath); + const ext = rel.split(".").pop()?.toLowerCase() ?? ""; + const contentType = SCENE_MIME[ext] ?? "application/octet-stream"; + return new Response(data, { headers: { "content-type": contentType } }); + } catch { /* try next */ } + } + return new Response(`Scene file not found: ${rel}`, { status: 404 }); + } + return serveDir(req, { fsRoot: distDir, quiet: true }); }); diff --git a/misc/DimSim/dimos-cli/cli.ts b/misc/DimSim/dimos-cli/cli.ts index af712b218e..ec1f0c3310 100644 --- a/misc/DimSim/dimos-cli/cli.ts +++ b/misc/DimSim/dimos-cli/cli.ts @@ -41,6 +41,10 @@ const AGENT_PY = CLI_DIR ? resolve(CLI_DIR, "agent.py") : null; * no assets need to be downloaded from GitHub releases, and npm/Node are * not required — Deno runs Vite directly. * + * Important for the vendored layout (misc/DimSim/ inside dimos): dist/ is + * gitignored and never committed, so on first run we have to materialize + * it ourselves rather than asking the user to `npm run build`. + * * --no-lock keeps the repo's deno.lock (which tracks only JSR deps for * the CLI) from being polluted with the frontend's npm dep graph. */ @@ -96,9 +100,8 @@ async function tryBuildFromSource( } } -/** Resolve distDir: use local dist/ if it exists (dev), else ~/.dimsim/dist/ (installed). */ +/** Resolve distDir: use local dist/ if it exists (dev / vendored), build it from sources if not, else fall back to ~/.dimsim/dist/. */ async function resolveDistDir(): Promise { - // Check local dist/ (only in dev mode, running from source) if (LOCAL_DIST_DIR && PROJECT_DIR) { try { await Deno.stat(`${LOCAL_DIST_DIR}/index.html`); @@ -118,8 +121,10 @@ async function resolveDistDir(): Promise { console.error(`[dimsim] No dist/ found.`); console.error(`[dimsim] Run 'dimsim setup' to download core assets.`); + if (!IS_REMOTE) { + console.error(`[dimsim] Or build locally with 'npm run build'.`); + } Deno.exit(1); - throw new Error("unreachable"); // for TS flow analysis — Deno.exit is never } function printUsage() { diff --git a/misc/DimSim/dimos-cli/deno.json b/misc/DimSim/dimos-cli/deno.json index a2c8e98d6f..a3c8dd2f9b 100644 --- a/misc/DimSim/dimos-cli/deno.json +++ b/misc/DimSim/dimos-cli/deno.json @@ -1,6 +1,6 @@ { "name": "@antim/dimsim", - "version": "0.2.2", + "version": "0.3.2", "description": "3D simulation environment for the dimos robotics stack. Browser-based Three.js + Rapier sim with LCM transport, sensor publishing, and eval harness.", "license": "MIT", "exports": { @@ -18,9 +18,7 @@ "vendor/", "setup.ts" ], - "exclude": [ - "test/" - ] + "exclude": ["test/"] }, "tasks": { "start": "deno run --allow-net --allow-read --unstable-net bridge/server.ts", diff --git a/misc/DimSim/dimos-cli/setup.ts b/misc/DimSim/dimos-cli/setup.ts index 8bd2665204..3c443c3548 100644 --- a/misc/DimSim/dimos-cli/setup.ts +++ b/misc/DimSim/dimos-cli/setup.ts @@ -156,33 +156,33 @@ export async function setup(localArchive?: string): Promise { const home = getDimsimHome(); const distDir = getDistDir(); - console.log(`[dimsim] Setting up in ${home}`); await Deno.mkdir(home, { recursive: true }); let registry: Registry | null = null; + let didWork = false; if (localArchive) { console.log(`[dimsim] Extracting core from local archive: ${localArchive}`); await extractTarGz(localArchive, distDir); + didWork = true; } else { registry = await fetchRegistry(); const local = await readVersionInfo(); - if (local.core === registry.version) { - console.log(`[dimsim] Core already up-to-date (v${registry.version})`); - } else { + if (local.core !== registry.version) { if (local.core) { console.log(`[dimsim] Updating core: v${local.core} → v${registry.version}`); + } else { + console.log(`[dimsim] Installing core v${registry.version}`); } const tmpFile = `${home}/core-download.tar.gz`; await download(registry.coreUrl, tmpFile); console.log(`[dimsim] Extracting core assets...`); await extractTarGz(tmpFile, distDir); await Deno.remove(tmpFile); - - // Write updated version local.core = registry.version; await writeVersionInfo(local); + didWork = true; } } @@ -206,21 +206,23 @@ export async function setup(localArchive?: string): Promise { installedEvalsVer = (await Deno.readTextFile(evalsVerFile)).trim(); } catch { /* not installed */ } - if (installedEvalsVer === registry.version) { - console.log(`[dimsim] Evals already up-to-date (v${registry.version})`); - } else { + if (installedEvalsVer !== registry.version) { const tmpFile = `${home}/evals-download.tar.gz`; - console.log(`[dimsim] Downloading evals...`); + console.log(`[dimsim] Updating evals → v${registry.version}`); await download(registry.evalsUrl, tmpFile); - console.log(`[dimsim] Extracting evals...`); await extractTarGz(tmpFile, evalsDir); await Deno.remove(tmpFile); await Deno.writeTextFile(evalsVerFile, registry.version); + didWork = true; } } - console.log(`[dimsim] Core setup complete.`); - console.log(`[dimsim] Install a scene: dimsim scene install apt`); + const verLabel = registry?.version ? `v${registry.version}` : "local"; + if (didWork) { + console.log(`[dimsim] core + evals ready (${verLabel})`); + } else { + console.log(`[dimsim] core + evals up-to-date (${verLabel})`); + } } export async function sceneInstall( diff --git a/misc/DimSim/dimos-cli/test/add_godcam.py b/misc/DimSim/dimos-cli/test/add_godcam.py new file mode 100644 index 0000000000..5b2028edf7 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/add_godcam.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Add a PIP god-view camera (top-down minimap) in the bottom-right corner.""" +import json, uuid, time, websocket + +ws = websocket.WebSocket() +ws.connect("ws://localhost:8090?ch=control") +ws.settimeout(10) + +mid = str(uuid.uuid4())[:8] +code = """ +// Remove existing PIP if re-running +if (window.__pipCleanup) { window.__pipCleanup(); } + +// God camera — orthographic top-down +const pipCam = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.1, 100); +pipCam.position.set(0, 40, 0); +pipCam.lookAt(0, 0, 0); + +// Monkey-patch renderer to add PIP pass after main render +const _origRender = renderer.render.bind(renderer); +const pipSize = 250; + +renderer.render = function(scn, cam) { + // Main render (full viewport) + _origRender(scn, cam); + + // PIP pass — bottom-right corner + const w = renderer.domElement.width; + const h = renderer.domElement.height; + const margin = 10; + + // Follow agent + const ap = agent.getPosition(); + pipCam.position.set(ap[0], 40, ap[2]); + pipCam.lookAt(ap[0], 0, ap[2]); + + renderer.setViewport(w - pipSize - margin, margin, pipSize, pipSize); + renderer.setScissor(w - pipSize - margin, margin, pipSize, pipSize); + renderer.setScissorTest(true); + renderer.autoClear = false; + _origRender(scn, pipCam); + renderer.autoClear = true; + renderer.setScissorTest(false); + renderer.setViewport(0, 0, w, h); +}; + +// Draw a border via CSS overlay +const border = document.createElement('div'); +border.id = 'pip-border'; +border.style.cssText = 'position:fixed;bottom:10px;right:10px;width:250px;height:250px;border:2px solid rgba(255,255,255,0.6);border-radius:4px;pointer-events:none;z-index:99999;'; +document.body.appendChild(border); + +// Cleanup function for re-running +window.__pipCleanup = () => { + renderer.render = _origRender; + border.remove(); + window.__pipCleanup = null; +}; + +return {godCam: true, pipSize} +""" + +ws.send(json.dumps({"type": "exec", "id": mid, "code": code})) + +deadline = time.time() + 10 +while time.time() < deadline: + raw = ws.recv() + if isinstance(raw, bytes): + continue + msg = json.loads(raw) + if msg.get("type") == "execResult" and msg.get("id") == mid: + if msg.get("success"): + print(f"OK: {msg['result']}") + else: + print(f"ERROR: {msg.get('error')}") + break + +ws.close() diff --git a/misc/DimSim/dimos-cli/test/add_purple_object.py b/misc/DimSim/dimos-cli/test/add_purple_object.py new file mode 100644 index 0000000000..b8e5a137c4 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/add_purple_object.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Add a purple object with physics collider near the agent.""" +import json, uuid, time, websocket + +ws = websocket.WebSocket() +ws.connect("ws://localhost:8090?ch=control") +ws.settimeout(10) + +mid = str(uuid.uuid4())[:8] +code = """ +const ap = agent.getPosition(); +const ax = ap[0], az = ap[2]; + +const group = new THREE.Group(); +group.name = "test-purple-object"; +const mat = new THREE.MeshStandardMaterial({color: 0x8B00FF, roughness: 0.3, metalness: 0.1}); + +// Shaft — cylinder +const shaft = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.5, 16), mat); +shaft.position.y = 0.35; +group.add(shaft); + +// Tip — hemisphere +const tip = new THREE.Mesh(new THREE.SphereGeometry(0.12, 16, 16), mat); +tip.position.y = 0.65; +group.add(tip); + +// Left ball +const ballL = new THREE.Mesh(new THREE.SphereGeometry(0.1, 16, 16), mat); +ballL.position.set(-0.12, 0.05, 0); +group.add(ballL); + +// Right ball +const ballR = new THREE.Mesh(new THREE.SphereGeometry(0.1, 16, 16), mat); +ballR.position.set(0.12, 0.05, 0); +group.add(ballR); + +group.position.set(ax + 2, 0.5, az); +scene.add(group); + +const info = addCollider(group, "box"); +return {name: group.name, pos: {x: +group.position.x.toFixed(1), y: +group.position.y.toFixed(1), z: +group.position.z.toFixed(1)}, collider: info} +""" + +ws.send(json.dumps({"type": "exec", "id": mid, "code": code})) + +deadline = time.time() + 10 +while time.time() < deadline: + raw = ws.recv() + if isinstance(raw, bytes): + continue + msg = json.loads(raw) + if msg.get("type") == "execResult" and msg.get("id") == mid: + if msg.get("success"): + print(f"Added: {msg['result']}") + else: + print(f"ERROR: {msg.get('error')}") + break + +ws.close() diff --git a/misc/DimSim/dimos-cli/test/diagnose_costmap.py b/misc/DimSim/dimos-cli/test/diagnose_costmap.py index 5323a355af..483f9cb337 100644 --- a/misc/DimSim/dimos-cli/test/diagnose_costmap.py +++ b/misc/DimSim/dimos-cli/test/diagnose_costmap.py @@ -1,18 +1,4 @@ #!/usr/bin/env python3 -# Copyright 2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """Diagnostic: subscribe to /global_costmap and /odom via LCM, print costmap stats. Run with the dimos venv while the agent + bridge are running: @@ -25,7 +11,6 @@ import sys import time - import numpy as np try: @@ -34,8 +19,8 @@ print("ERROR: 'lcm' Python package not found. Run with dimos venv.") sys.exit(1) -from dimos_lcm.geometry_msgs.PoseStamped import PoseStamped as LCMPoseStamped from dimos_lcm.nav_msgs.OccupancyGrid import OccupancyGrid as LCMOccupancyGrid +from dimos_lcm.geometry_msgs.PoseStamped import PoseStamped as LCMPoseStamped class CostmapDiagnostic: @@ -82,21 +67,21 @@ def analyze(self): other_count = int(np.sum((grid != 0) & (grid != 100) & (grid != -1))) total = w * h - print(f"\n{'=' * 60}") + print(f"\n{'='*60}") print(f"COSTMAP #{self.costmap_count} ({w}x{h} cells, {res:.3f} m/cell)") print(f"Origin: ({ox:.2f}, {oy:.2f})") - print(f"World extent: X=[{ox:.2f}, {ox + w * res:.2f}] Y=[{oy:.2f}, {oy + h * res:.2f}]") - print(f" FREE (0): {free_count:>8} ({100 * free_count / total:.1f}%)") - print(f" OCCUPIED (100): {occupied_count:>8} ({100 * occupied_count / total:.1f}%)") - print(f" UNKNOWN (-1): {unknown_count:>8} ({100 * unknown_count / total:.1f}%)") - print(f" OTHER (1-99): {other_count:>8} ({100 * other_count / total:.1f}%)") + print(f"World extent: X=[{ox:.2f}, {ox + w*res:.2f}] Y=[{oy:.2f}, {oy + h*res:.2f}]") + print(f" FREE (0): {free_count:>8} ({100*free_count/total:.1f}%)") + print(f" OCCUPIED (100): {occupied_count:>8} ({100*occupied_count/total:.1f}%)") + print(f" UNKNOWN (-1): {unknown_count:>8} ({100*unknown_count/total:.1f}%)") + print(f" OTHER (1-99): {other_count:>8} ({100*other_count/total:.1f}%)") if other_count > 0: mask = (grid != 0) & (grid != 100) & (grid != -1) other_vals = grid[mask] unique, counts = np.unique(other_vals, return_counts=True) - print(" Other cost distribution (top 10):") - for v, c in sorted(zip(unique, counts, strict=False), key=lambda x: -x[1])[:10]: + print(f" Other cost distribution (top 10):") + for v, c in sorted(zip(unique, counts), key=lambda x: -x[1])[:10]: print(f" cost={v:>4}: {c} cells") # Check robot position @@ -111,24 +96,23 @@ def analyze(self): if 0 <= gx < w and 0 <= gy < h: print(f"Robot cell cost: {grid[gy, gx]}") r = 5 - region = grid[max(0, gy - r) : gy + r + 1, max(0, gx - r) : gx + r + 1] + region = grid[max(0,gy-r):gy+r+1, max(0,gx-r):gx+r+1] rfree = int(np.sum(region == 0)) rocc = int(np.sum(region == 100)) runk = int(np.sum(region == -1)) roth = int(np.sum((region != 0) & (region != 100) & (region != -1))) print(f"11x11 neighborhood: FREE={rfree} OCC={rocc} UNK={runk} OTHER={roth}") else: - print("WARNING: Robot is OUTSIDE costmap bounds!") + print(f"WARNING: Robot is OUTSIDE costmap bounds!") else: print(f"\nNo odom received (count={self.odom_count})") # Frontier analysis - unk_mask = grid == -1 - free_mask = grid == 0 - occ_mask = grid >= 100 + unk_mask = (grid == -1) + free_mask = (grid == 0) + occ_mask = (grid >= 100) from scipy import ndimage - kernel = np.ones((3, 3)) free_dilated = ndimage.binary_dilation(free_mask, structure=kernel) occ_dilated = ndimage.binary_dilation(occ_mask, structure=kernel) @@ -139,7 +123,7 @@ def analyze(self): frontier_count = int(np.sum(frontier_eligible)) frontier_blocked = unknown_near_free - frontier_count - print("\nFRONTIER ANALYSIS (pre-inflation):") + print(f"\nFRONTIER ANALYSIS (pre-inflation):") print(f" Unknown cells adjacent to free: {unknown_near_free}") print(f" ...blocked by adjacent occupied: {frontier_blocked}") print(f" VALID FRONTIER CELLS: {frontier_count}") @@ -148,29 +132,27 @@ def analyze(self): if frontier_count > 0: inflate_radius = 0.25 # meters, default in frontier explorer cell_radius = int(np.ceil(inflate_radius / res)) - y, x = np.ogrid[-cell_radius : cell_radius + 1, -cell_radius : cell_radius + 1] + y, x = np.ogrid[-cell_radius:cell_radius+1, -cell_radius:cell_radius+1] inflate_kernel = (x**2 + y**2 <= cell_radius**2).astype(np.uint8) inflated_occ = ndimage.binary_dilation(occ_mask, structure=inflate_kernel) inflated_occ_dilated = ndimage.binary_dilation(inflated_occ, structure=kernel) frontier_after_inflate = candidates & ~inflated_occ_dilated - print("\n After 0.25m inflation:") - print( - f" VALID FRONTIER CELLS: {int(np.sum(frontier_after_inflate))}" - ) + print(f"\n After 0.25m inflation:") + print(f" VALID FRONTIER CELLS: {int(np.sum(frontier_after_inflate))}") if frontier_count == 0 and unknown_near_free > 0: print(f"\n *** DIAGNOSIS: ALL {unknown_near_free} unknown-near-free cells") - print(" are also adjacent to occupied cells. Obstacles border every") - print(" free/unknown boundary. The height_cost algorithm may produce") - print(" high-gradient costs at edges, or the LiDAR sees obstacles") - print(" exactly at the boundary of observed space.") + print(f" are also adjacent to occupied cells. Obstacles border every") + print(f" free/unknown boundary. The height_cost algorithm may produce") + print(f" high-gradient costs at edges, or the LiDAR sees obstacles") + print(f" exactly at the boundary of observed space.") elif frontier_count == 0 and free_count == 0: - print("\n *** DIAGNOSIS: No FREE (cost=0) cells at all!") - print(" The height_cost algorithm is not seeing flat ground.") - print(" Check if LiDAR produces ground-hitting points (Z<0.1 in robotics frame).") + print(f"\n *** DIAGNOSIS: No FREE (cost=0) cells at all!") + print(f" The height_cost algorithm is not seeing flat ground.") + print(f" Check if LiDAR produces ground-hitting points (Z<0.1 in robotics frame).") elif frontier_count == 0 and unknown_near_free == 0 and free_count > 0: - print("\n *** DIAGNOSIS: FREE cells exist but no UNKNOWN cells border them.") - print(" The free space is fully enclosed by occupied/other-cost cells.") + print(f"\n *** DIAGNOSIS: FREE cells exist but no UNKNOWN cells border them.") + print(f" The free space is fully enclosed by occupied/other-cost cells.") def run(self): self.lc.subscribe("/global_costmap#nav_msgs.OccupancyGrid", self._on_costmap) diff --git a/misc/DimSim/dimos-cli/test/dimos_integration.py b/misc/DimSim/dimos-cli/test/dimos_integration.py index 98ceb90364..2c0123f269 100755 --- a/misc/DimSim/dimos-cli/test/dimos_integration.py +++ b/misc/DimSim/dimos-cli/test/dimos_integration.py @@ -1,18 +1,4 @@ #!/usr/bin/env python3 -# Copyright 2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """ DimSim ↔ dimos Integration Test (UDP Multicast) @@ -41,15 +27,16 @@ --rate N cmd_vel publish rate in Hz (default: 10) """ -import argparse -import socket -import struct import sys -import threading import time +import struct +import socket +import threading +import argparse # dimos message types for encoding cmd_vel -from dimos.msgs.geometry_msgs import Twist, Vector3 +from dimos.msgs.geometry_msgs.Twist import Twist +from dimos.msgs.geometry_msgs.Vector3 import Vector3 # -- LCM constants ------------------------------------------------------------ LCM_MAGIC = 0x4C433032 # "LC02" in ASCII / big-endian @@ -59,7 +46,6 @@ # -- LCM packet codec (matches @dimos/msgs encodePacket / decodePacket) -------- - def encode_lcm_packet(channel: str, payload: bytes) -> bytes: """Encode an LCM binary packet (same format as @dimos/msgs encodePacket).""" global _seq @@ -78,16 +64,16 @@ def decode_lcm_packet(data: bytes) -> tuple[str, bytes]: raise ValueError(f"Bad magic: 0x{magic:08x}") null_pos = data.index(0, 8) channel = data[8:null_pos].decode("utf-8") - payload = data[null_pos + 1 :] + payload = data[null_pos + 1:] return channel, payload # -- Channel names (must match DimSim's dimosBridge.ts) ------------------------ CH_CMD_VEL = "/cmd_vel#geometry_msgs.Twist" -CH_ODOM = "/odom#geometry_msgs.PoseStamped" -CH_IMAGE = "/camera/image#sensor_msgs.Image" -CH_DEPTH = "/camera/depth#sensor_msgs.Image" -CH_LIDAR = "/lidar/points#sensor_msgs.PointCloud2" +CH_ODOM = "/odom#geometry_msgs.PoseStamped" +CH_IMAGE = "/camera/image#sensor_msgs.Image" +CH_DEPTH = "/camera/depth#sensor_msgs.Image" +CH_LIDAR = "/lidar/points#sensor_msgs.PointCloud2" def create_mcast_recv_socket() -> socket.socket: @@ -130,7 +116,7 @@ def main(): print(f"[integration] LCM multicast {MCAST_GRP}:{MCAST_PORT}") print(f"[integration] Publishing /cmd_vel at {args.rate} Hz") - print("[integration] Listening for sensor data on multicast") + print(f"[integration] Listening for sensor data on multicast") print(f"[integration] Timeout: {args.timeout}s\n") # -- Receive thread -------------------------------------------------------- @@ -138,7 +124,7 @@ def recv_loop(): while running: try: data, addr = recv_sock.recvfrom(65536) - except TimeoutError: + except socket.timeout: continue except OSError: break @@ -190,12 +176,10 @@ def recv_loop(): # Status check every 5s elapsed = time.time() - start_time if tick > 1 and (tick % (args.rate * 5) == 0): - print( - f"\n[integration] STATUS ({elapsed:.0f}s): " - f"cmd_sent={tick} odom={received['odom']} " - f"rgb={received['image']} depth={received['depth']} " - f"lidar={received['lidar']}" - ) + print(f"\n[integration] STATUS ({elapsed:.0f}s): " + f"cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}") if all(v > 0 for v in received.values()): success = True @@ -215,11 +199,9 @@ def recv_loop(): if not success: print(f"\n[integration] TIMEOUT after {args.timeout}s") - print( - f"[integration] Final: cmd_sent={tick} odom={received['odom']} " - f"rgb={received['image']} depth={received['depth']} " - f"lidar={received['lidar']}" - ) + print(f"[integration] Final: cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}") except KeyboardInterrupt: print("\n[integration] Interrupted by user") diff --git a/misc/DimSim/dimos-cli/test/lcm_cross_test.py b/misc/DimSim/dimos-cli/test/lcm_cross_test.py index d1694f0d31..1513734de9 100644 --- a/misc/DimSim/dimos-cli/test/lcm_cross_test.py +++ b/misc/DimSim/dimos-cli/test/lcm_cross_test.py @@ -1,34 +1,15 @@ #!/usr/bin/env python3 -# Copyright 2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """Quick test: can Python receive LCM messages from Deno?""" - -import sys -import time - +import sys, time, threading sys.path.insert(0, "/Users/viswajitnair/Desktop/4Wall.nosync/Dimensional/dimos") import lcm received = {"count": 0} - def handler(channel, data): received["count"] += 1 print(f"[py] Got message #{received['count']} on {channel} ({len(data)} bytes)") - lc = lcm.LCM("udpm://239.255.76.67:7667?ttl=0") lc.subscribe(".*lcm_cross_test.*", handler) diff --git a/misc/DimSim/dimos-cli/test/list_assets.py b/misc/DimSim/dimos-cli/test/list_assets.py new file mode 100644 index 0000000000..29a6463906 --- /dev/null +++ b/misc/DimSim/dimos-cli/test/list_assets.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""List top-level asset groups in the scene (the actual furniture/objects).""" +import json, uuid, time, websocket + +ws = websocket.WebSocket() +ws.connect("ws://localhost:8090?ch=control") +ws.settimeout(10) + +msg_id = str(uuid.uuid4())[:8] +code = """ +const ag = scene.getObjectByName("assetsGroup"); +if (!ag) return {error: "no assetsGroup"}; +const items = []; +for (const child of ag.children) { + let label = ""; + child.traverse(obj => { + if (!label && obj.name) { + const n = obj.name; + if (n.includes("assetGroup:")) { + const parts = n.split(":"); + if (parts.length >= 3) label = parts[2]; + } else if (n.includes("assetPrim:")) { + const parts = n.split(":"); + if (parts.length >= 3 && !label) label = parts[2]; + } + } + }); + let meshCount = 0; + child.traverse(obj => { if (obj.isMesh) meshCount++; }); + items.push({ + id: child.name || "(no name)", + label: label || "(unnamed)", + pos: {x: +child.position.x.toFixed(1), y: +child.position.y.toFixed(1), z: +child.position.z.toFixed(1)}, + meshCount + }); +} +return items; +""" +ws.send(json.dumps({"type": "exec", "id": msg_id, "code": code})) + +deadline = time.time() + 10 +while time.time() < deadline: + raw = ws.recv() + if isinstance(raw, bytes): + continue + msg = json.loads(raw) + if msg.get("type") == "execResult" and msg.get("id") == msg_id: + if not msg.get("success"): + print(f"ERROR: {msg.get('error')}") + break + assets = msg.get("result", []) + print(f"Found {len(assets)} assets:\n") + for i, a in enumerate(assets): + print(f" [{i:2d}] {a['label']:30s} pos=({a['pos']['x']}, {a['pos']['y']}, {a['pos']['z']}) meshes={a['meshCount']}") + print(f" id: {a['id']}") + break + +ws.close() diff --git a/misc/DimSim/dimos-cli/test/list_scene.py b/misc/DimSim/dimos-cli/test/list_scene.py new file mode 100644 index 0000000000..797a4c4a1b --- /dev/null +++ b/misc/DimSim/dimos-cli/test/list_scene.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""List all named objects in the scene.""" +import json, uuid, time, websocket + +ws = websocket.WebSocket() +ws.connect("ws://localhost:8090?ch=control") +ws.settimeout(10) + +msg_id = str(uuid.uuid4())[:8] +code = """ +const items = []; +scene.traverse(obj => { + if (obj.name) items.push({name: obj.name, type: obj.type, depth: 0}); +}); +return items; +""" +ws.send(json.dumps({"type": "exec", "id": msg_id, "code": code})) + +deadline = time.time() + 10 +while time.time() < deadline: + raw = ws.recv() + if isinstance(raw, bytes): + continue + msg = json.loads(raw) + if msg.get("type") == "execResult" and msg.get("id") == msg_id: + for obj in msg.get("result", []): + print(f" {obj['type']:20s} {obj['name']}") + break + +ws.close() diff --git a/misc/DimSim/dimos-cli/test/scene_editor_test.py b/misc/DimSim/dimos-cli/test/scene_editor_test.py index 33466b426d..fa4062fde3 100644 --- a/misc/DimSim/dimos-cli/test/scene_editor_test.py +++ b/misc/DimSim/dimos-cli/test/scene_editor_test.py @@ -1,18 +1,4 @@ #!/usr/bin/env python3 -# Copyright 2026 Dimensional Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - """Integration test for SceneEditor — script execution engine. Requires dimsim running headless on port 8090: @@ -29,6 +15,7 @@ import websocket + PORT = 8090 WS_URL = f"ws://localhost:{PORT}?ch=control" @@ -162,9 +149,7 @@ def test_agent_access(ws): r = send_exec(ws, "const p = agent.getPosition(); return {x: p[0], y: p[1], z: p[2]}") assert r["success"], f"exec failed: {r.get('error')}" assert "x" in r["result"], f"unexpected: {r['result']}" - print( - f" PASS — agent at ({r['result']['x']:.2f}, {r['result']['y']:.2f}, {r['result']['z']:.2f})" - ) + print(f" PASS — agent at ({r['result']['x']:.2f}, {r['result']['y']:.2f}, {r['result']['z']:.2f})") def test_add_light(ws): @@ -209,7 +194,7 @@ def test_modify_object(ws): assert r["success"], f"exec failed: {r.get('error')}" assert r["result"]["pos"] == {"x": 7, "y": 2, "z": 7}, f"position wrong: {r['result']}" assert r["result"]["scale"] == {"x": 2, "y": 2, "z": 2}, f"scale wrong: {r['result']}" - assert r["result"]["color"] == 0x00FF00, f"color wrong: {r['result']}" + assert r["result"]["color"] == 0x00ff00, f"color wrong: {r['result']}" print(f" PASS — modified: {r['result']}") @@ -338,13 +323,10 @@ def test_remove_collider(ws): print(f" PASS — removed: {r['result']}") # Verify double-remove returns false - r2 = send_exec( - ws, - """ + r2 = send_exec(ws, """ const mesh = scene.getObjectByName("test-physics-box"); return {removed: removeCollider(mesh)} -""", - ) +""") assert r2["success"] and r2["result"]["removed"] is False, "Double remove should return false" print(" PASS — double remove returns false") @@ -410,16 +392,13 @@ def test_add_npc_idle(ws): def test_remove_npc(ws): """Test: removeNPC removes NPC and cleans up.""" print(" [21] NPC: removeNPC") - r = send_exec( - ws, - """ + r = send_exec(ws, """ removeNPC('test-npc-idle'); // Check immediately in same exec — name was cleared by removeNPC const npcs = []; scene.traverse(obj => { if (obj.name === 'test-npc-idle') npcs.push(obj.name); }); return { removed: true, remaining: npcs.length } -""", - ) +""") assert r["success"], f"exec failed: {r.get('error')}" assert r["result"]["remaining"] == 0, f"NPC still found: {r['result']}" print(f" PASS — removed and verified: {r['result']}") @@ -437,9 +416,7 @@ def test_embodiment_config(ws): assert "radius" in cfg, f"no radius: {cfg}" assert "halfHeight" in cfg, f"no halfHeight: {cfg}" assert "type" in cfg, f"no type: {cfg}" - print( - f" PASS — embodiment: type={cfg['type']} radius={cfg['radius']} halfHeight={cfg['halfHeight']}" - ) + print(f" PASS — embodiment: type={cfg['type']} radius={cfg['radius']} halfHeight={cfg['halfHeight']}") def main(): @@ -489,7 +466,7 @@ def main(): failed += 1 ws.close() - print(f"\n{'=' * 50}") + print(f"\n{'='*50}") print(f"Results: {passed} passed, {failed} failed out of {len(tests)}") sys.exit(1 if failed else 0) diff --git a/misc/DimSim/docs/sdk-design.md b/misc/DimSim/docs/sdk-design.md new file mode 100644 index 0000000000..62b3851c40 --- /dev/null +++ b/misc/DimSim/docs/sdk-design.md @@ -0,0 +1,253 @@ +# DimSim Scene SDK — Design + Tickets + +**Issue**: dimensionalOS/dimos#1691 (Simulation Editing) +**Date**: 2026-03-27 + +--- + +## Coverage Matrix + +Every request from #1691 mapped to a ticket: + +| #1691 Request | Ticket | +|---------------|--------| +| Model importing (GLTFLoader API) | SDK-1 | +| Standard Three.js API (primitives, scenegraph, materials, textures, lights, cameras, shadows, fog) | SDK-1 | +| Optional physics for models (collisions toggle) | SDK-1, ENG-1 | +| Robot definition API (drone, holonomic, plane, car) | SDK-2 | +| Validation: deploy robot in random Three.js environment | VAL-1 | +| Validation: third-party map load (Sketchfab GLB) | VAL-1 | +| Validation: add red ball with collisions | VAL-1 | +| Validation: editing flow (change position, reload, check) | ENG-2 | +| Validation: new embodiment (code a drone) | VAL-2 | + +--- + +## Architecture + +``` +Developer code (TypeScript) DimSim Runtime (browser) +┌─────────────────────────┐ ┌──────────────────────┐ +│ import { Scene, Robot │ │ engine.js │ +│ } from "dimsim/sdk" │ │ importLevelFromJSON()│ +│ │ JSON │ AiAvatar / Robot cfg │ +│ scene.addModel(...) │ ──────► │ Rapier physics │ +│ scene.addBox(...) │ │ Three.js renderer │ +│ scene.setRobot(...) │ │ Eval harness │ +│ scene.export("x.json") │ └──────────────────────┘ +└─────────────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ dimsim dev --scene x │ +│ dimsim eval --scene x │ +└─────────────────────────┘ +``` + +**Key principle**: The SDK outputs DimSim's existing scene JSON format. +The runtime already consumes it. Most tickets need zero engine changes. + +--- + +## Tickets + +### SDK-1: Scene builder API — models, primitives, lights, materials, physics + +**What**: TypeScript `Scene` class that builds scene JSON. Covers model importing, Three.js primitives, materials, lights, optional physics — the core of the issue. + +**Scope**: +- `Scene` class with `addBox`, `addSphere`, `addCylinder`, `addCone`, `addTorus`, `addPlane` +- `addModel(id, { url, physics, collider, position, rotation, scale })` — GLB/GLTF by URL +- `addPointLight`, `addDirectionalLight`, `addSpotLight`, `addAmbientLight` +- `sky({ topColor, horizonColor, bottomColor, brightness, sunStrength })` +- `fog({ color, near, far })` — Three.js fog +- `addTag(name, { position })` — navigation waypoints for evals +- `group(name)` — scenegraph grouping, children inherit transform +- `setSpawnPoint({ x, y, z, yaw })` +- PBR materials: `{ color, roughness, metalness, specularIntensity, envMapIntensity }` +- Shadow config per object: `castShadow`, `receiveShadow` +- Physics per object: `physics: true/false`, `dynamic: true/false`, `collider: "auto" | "box" | "trimesh" | "convex"` +- `scene.export(filepath)` writes JSON, `scene.toJSON()` returns raw object +- `dimsim dev --scene ./file.json` accepts local file paths (not just S3 manifest names) +- Published as `@antim/dimsim/sdk` export from existing JSR package + +```typescript +import { Scene } from "@antim/dimsim/sdk"; + +const scene = new Scene("fps-arena"); +scene.addModel("map", { url: "./models/lowpoly-fps-map.glb", physics: true }); +scene.addSphere("ball", { + radius: 0.3, position: [2, 2, 0], + material: { color: "#FF0000" }, + physics: true, dynamic: true, +}); +scene.addPointLight("sun", { position: [0, 10, 0], intensity: 3 }); +scene.setSpawnPoint({ x: 0, y: 0.5, z: 3 }); +await scene.export("./scenes/fps-arena.json"); +``` + +**Files**: `sdk/mod.ts`, `sdk/scene.ts`, `sdk/primitives.ts`, `sdk/model.ts`, `sdk/light.ts`, `sdk/types.ts`, `cli.ts` (--scene filepath) + +--- + +### SDK-2: Robot definition in scene JSON + +**What**: `robot` field in scene JSON so developers configure the agent type, collider, avatar, and controller from code. AiAvatar reads this at spawn instead of using hardcoded values. + +**Scope**: +- SDK: `scene.setRobot(Robot.capsule({ ... }))`, `Robot.drone({ ... })`, `Robot.car({ ... })`, `Robot.holonomic({ ... })` +- Scene JSON gets `robot` field: + ```json + { "robot": { "type": "drone", "radius": 0.15, "height": 0.08, "avatar": "./drone.glb", "walkSpeed": 5.0, "hoverHeight": 1.5 } } + ``` +- AiAvatar reads `sceneJson.robot` for collider size, avatar GLB, speed, and controller type +- `capsule` = current behavior (kinematic character controller) +- `drone` = same controller but Y-axis movement enabled, hover height default, cmd_vel.linear.z maps to vertical +- `holonomic` = omnidirectional, cmd_vel.linear.y maps to strafe +- `car` = box collider, linear.x = throttle, angular.z = steering angle +- All types consume standard `cmd_vel` Twist — dimos nav stack works unchanged + +```typescript +scene.setRobot(Robot.drone({ + radius: 0.15, + height: 0.08, + walkSpeed: 5.0, + hoverHeight: 2.0, + avatar: "./models/quadcopter.glb", +})); +``` + +**Files**: `sdk/robot.ts`, `src/AiAvatar.js` (read config from scene), `src/engine.js` (pass robot config) + +--- + +### ENG-1: Dynamic rigid bodies in engine + +**What**: Support `dynamic: true` on primitives and models so objects respond to gravity and collisions (not just static walls/floors). + +**Scope**: +- Currently all colliders use `RigidBodyDesc.fixed()` (immovable) +- Add `RigidBodyDesc.dynamic()` path when scene JSON has `dynamic: true` +- Expose `mass`, `friction`, `restitution` in primitive/model JSON +- Rapier already supports all of this — just need to wire the properties through + +**Covers**: "Add a red ball with collisions enabled" — ball should fall and roll, not float in place. + +**Files**: `src/engine.js` (collider builder sections: `buildPrimitiveCollider`, `buildRapierTriMeshColliderFromObject`) + +--- + +### ENG-2: Hot reload (`--watch`) + +**What**: `dimsim dev --scene ./file.json --watch` watches the JSON file and auto-reloads when it changes. The "reasonable editing flow" from the issue. + +**Scope**: +- `Deno.watchFs()` on scene file path in bridge server +- On change: read new JSON, send `{ type: "reloadScene", scene: }` to browser via WS +- Browser calls `importLevelFromJSON()` (already exists) +- Developer flow: edit `build-scene.ts` → save → re-run → scene JSON updates → browser reloads automatically +- ~20 lines of code in bridge server + +**Covers**: "change the position of an object, reload, check, add a light, reload" + +**Files**: `bridge/server.ts`, `cli.ts` (--watch flag) + +--- + +### VAL-1: Demo scenes — Three.js env, Sketchfab map, custom objects + +**What**: Three example scripts proving the SDK works end-to-end. Each one is `deno run examples/X.ts` → produces JSON → `dimsim dev --scene ./output.json`. + +**Scope**: +- `examples/threejs-env.ts` — load a Three.js example GLB (e.g. LittlestTokyo), add lights, deploy robot +- `examples/sketchfab-map.ts` — load a Sketchfab low-poly FPS map GLB with physics, deploy robot +- `examples/custom-scene.ts` — floor + walls + red ball (dynamic) + lights + fog, robot navigates +- README with run instructions + +**Covers**: All three validation scenarios from #1691 + +**Files**: `examples/*.ts`, `examples/README.md` + +--- + +### VAL-2: Demo — drone embodiment + +**What**: Example script deploying a drone robot in a scene, validating the robot definition API works. + +**Scope**: +- `examples/drone-demo.ts` — scene with obstacles at varying heights, drone robot config +- Demonstrates: drone avatar loads, drone hovers at configured height, cmd_vel.linear.z controls altitude +- Should require "similar amount of code to doing it in plain Three.js" per the issue + +```typescript +const scene = new Scene("drone-test"); +scene.addModel("building", { url: "./models/building.glb", physics: true }); +scene.setRobot(Robot.drone({ hoverHeight: 2.0, avatar: "./models/drone.glb" })); +scene.setSpawnPoint({ x: 0, y: 2, z: 0 }); +await scene.export("./scenes/drone-test.json"); +``` + +**Covers**: "Code a drone, deploy within a world above" + +**Files**: `examples/drone-demo.ts` + +--- + +## Dependency Graph + +``` +SDK-1 (scene builder) ─────┬──► VAL-1 (3 demo scenes) + │ +SDK-2 (robot config) ──────┼──► VAL-2 (drone demo) + │ +ENG-1 (dynamic bodies) ─────┘ +ENG-2 (hot reload) ── independent +``` + +**Build order**: SDK-1 → (ENG-1 + ENG-2 + SDK-2 in parallel) → (VAL-1 + VAL-2) + +--- + +## File Structure + +``` +DimSim/ +├── sdk/ +│ ├── mod.ts # Public API: export { Scene, Robot } +│ ├── scene.ts # Scene builder class +│ ├── primitives.ts # Primitive type definitions + helpers +│ ├── model.ts # GLB/GLTF model wrapper +│ ├── light.ts # Light types +│ ├── robot.ts # Robot.capsule(), Robot.drone(), etc. +│ └── types.ts # Vec3, Transform, Material, etc. +├── examples/ +│ ├── threejs-env.ts +│ ├── sketchfab-map.ts +│ ├── custom-scene.ts +│ ├── drone-demo.ts +│ └── README.md +├── dimos-cli/ +│ ├── cli.ts # --scene filepath, --watch flag +│ ├── bridge/server.ts # hot reload watcher +│ └── ... +├── src/ +│ ├── engine.js # dynamic bodies, robot config passthrough +│ ├── AiAvatar.js # read robot config from scene JSON +│ └── ... +└── docs/ + └── sdk-design.md # this file +``` + +## JSR exports + +```json +{ + "exports": { + ".": "./cli.ts", + "./mod": "./mod.ts", + "./sdk": "./sdk/mod.ts" + } +} +``` + +Usage: `import { Scene, Robot } from "@antim/dimsim/sdk";` diff --git a/misc/DimSim/evals/apt/go-to-couch.json b/misc/DimSim/evals/apt/go-to-couch.json index 74d4535487..f300e93b3b 100644 --- a/misc/DimSim/evals/apt/go-to-couch.json +++ b/misc/DimSim/evals/apt/go-to-couch.json @@ -2,12 +2,7 @@ "name": "go-to-couch", "environment": "apt", "task": "Go to the couch", - "startPose": { - "x": 0, - "y": 0.5, - "z": 3, - "yaw": 0 - }, + "startPose": { "x": 0, "y": 0.5, "z": 3, "yaw": 0 }, "timeoutSec": 30, "successCriteria": { "objectDistance": { diff --git a/misc/DimSim/evals/apt/go-to-kitchen.json b/misc/DimSim/evals/apt/go-to-kitchen.json index 7e68734583..47da614609 100644 --- a/misc/DimSim/evals/apt/go-to-kitchen.json +++ b/misc/DimSim/evals/apt/go-to-kitchen.json @@ -2,12 +2,7 @@ "name": "go-to-kitchen", "environment": "apt", "task": "Go to the kitchen", - "startPose": { - "x": 0, - "y": 0.5, - "z": 3, - "yaw": 0 - }, + "startPose": { "x": 0, "y": 0.5, "z": 3, "yaw": 0 }, "timeoutSec": 30, "successCriteria": { "objectDistance": { diff --git a/misc/DimSim/evals/apt/go-to-tv.json b/misc/DimSim/evals/apt/go-to-tv.json index ba07f96167..259cfcc3ef 100644 --- a/misc/DimSim/evals/apt/go-to-tv.json +++ b/misc/DimSim/evals/apt/go-to-tv.json @@ -2,12 +2,7 @@ "name": "go-to-tv", "environment": "apt", "task": "Go to the TV", - "startPose": { - "x": 0, - "y": 0.5, - "z": 3, - "yaw": 0 - }, + "startPose": { "x": 0, "y": 0.5, "z": 3, "yaw": 0 }, "timeoutSec": 30, "successCriteria": { "objectDistance": { diff --git a/misc/DimSim/index.html b/misc/DimSim/index.html index 9d4f457b75..57aa0b1dfe 100644 --- a/misc/DimSim/index.html +++ b/misc/DimSim/index.html @@ -32,7 +32,7 @@ DimSim - +
@@ -127,7 +127,7 @@
- + - + diff --git a/misc/DimSim/public/_dimsim/eval-api.js b/misc/DimSim/public/_dimsim/eval-api.js new file mode 100644 index 0000000000..70ac5f7af1 --- /dev/null +++ b/misc/DimSim/public/_dimsim/eval-api.js @@ -0,0 +1,31 @@ +/** + * @dimsim/eval — public ESM facade for eval workflows. + * + * Eval files under `scenes//evals/*.js` import `runEval` from here and + * call it directly: + * + * import { runEval } from '@dimsim/eval'; + * await runEval({ + * scene: 'apartment', + * task: 'Go to the couch', + * success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional' }), + * }); + * + * The import map in index.html aliases `@dimsim/eval` to this file. At + * runtime we wait for the engine to wire up `window.__dimsim.eval.runEval` + * (it dispatches a `dimsim-eval-ready` event when it's available) and then + * delegate the call. This indirection keeps the public surface decoupled + * from the bundled engine chunk's hashed filename. + */ + +async function _ready() { + if (window.__dimsim?.eval?.runEval) return; + await new Promise((resolve) => { + window.addEventListener('dimsim-eval-ready', resolve, { once: true }); + }); +} + +export async function runEval(workflow) { + await _ready(); + return window.__dimsim.eval.runEval(workflow); +} diff --git a/misc/DimSim/scenes/apartment/evals/go-to-couch.js b/misc/DimSim/scenes/apartment/evals/go-to-couch.js index 2fe18a2f49..30b36db952 100644 --- a/misc/DimSim/scenes/apartment/evals/go-to-couch.js +++ b/misc/DimSim/scenes/apartment/evals/go-to-couch.js @@ -1,7 +1,9 @@ -export default { +import { runEval } from '@dimsim/eval'; + +await runEval({ scene: 'apartment', task: 'Go to the couch', timeoutSec: 30, startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional', thresholdM: 2.0 }), -}; +}); diff --git a/misc/DimSim/scenes/apartment/evals/go-to-kitchen.js b/misc/DimSim/scenes/apartment/evals/go-to-kitchen.js index af8d2084c0..5165406385 100644 --- a/misc/DimSim/scenes/apartment/evals/go-to-kitchen.js +++ b/misc/DimSim/scenes/apartment/evals/go-to-kitchen.js @@ -1,7 +1,9 @@ -export default { +import { runEval } from '@dimsim/eval'; + +await runEval({ scene: 'apartment', task: 'Go to the kitchen', timeoutSec: 30, startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, success: (ctx) => ctx.rubrics.objectDistance({ target: 'refrigerator', thresholdM: 3.0 }), -}; +}); diff --git a/misc/DimSim/scenes/apartment/evals/go-to-tv.js b/misc/DimSim/scenes/apartment/evals/go-to-tv.js index 43d3098198..c9800d80e5 100644 --- a/misc/DimSim/scenes/apartment/evals/go-to-tv.js +++ b/misc/DimSim/scenes/apartment/evals/go-to-tv.js @@ -1,7 +1,9 @@ -export default { +import { runEval } from '@dimsim/eval'; + +await runEval({ scene: 'apartment', task: 'Go to the TV', timeoutSec: 30, startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, success: (ctx) => ctx.rubrics.objectDistance({ target: 'television', thresholdM: 2.0 }), -}; +}); diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index 2b18177a84..bc95f44bc1 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7587,6 +7587,12 @@ if (dimosMode) { }, }); window.__evalHarness = evalHarness; + // Expose runEval(workflow) so workflow files imported from + // `@dimsim/eval` (via the import map → public/_dimsim/eval-api.js) + // can drive evaluations directly with top-level await. + window.__dimsim = window.__dimsim || {}; + window.__dimsim.eval = { runEval: (wf) => evalHarness.runEval(wf) }; + window.dispatchEvent(new Event('dimsim-eval-ready')); // Scene editor — script execution engine for sim editing (exec_js API) const { SceneEditor } = await import("./sceneEditor.ts"); diff --git a/misc/DimSim/src/evals/harness.ts b/misc/DimSim/src/evals/harness.ts index fa3b841456..7fbb218027 100644 --- a/misc/DimSim/src/evals/harness.ts +++ b/misc/DimSim/src/evals/harness.ts @@ -145,7 +145,7 @@ export class EvalHarness { if (this.channel && cmd.channel && cmd.channel !== this.channel) return; switch (cmd.type) { case "runEval": - await this.runEval(cmd.workflowUrl); + await this._loadAndRunWorkflowFile(cmd.workflowUrl); break; case "ping": this._send({ type: "pong", ts: Date.now() }); @@ -153,49 +153,68 @@ export class EvalHarness { } } - // ── Public entry point ───────────────────────────────────────────────────── + /** + * WS-driven entry: dynamic-import a workflow file. The file's top-level + * `await runEval({...})` (via the `@dimsim/eval` import map) calls + * `this.runEval(workflow)` and sends the result WS message itself. We + * just await the import — when it resolves the eval is done. + */ + async _loadAndRunWorkflowFile(workflowUrl: string): Promise { + try { + const cacheBust = `?t=${Date.now()}`; + await import(/* @vite-ignore */ workflowUrl + cacheBust); + } catch (e: any) { + console.error(`[eval] failed to import ${workflowUrl}:`, e); + this._send({ + type: "evalResult", workflowUrl, scene: "", task: "", + passed: false, reason: `import failed: ${e?.message ?? e}`, + durationMs: 0, + }); + } + } + + // ── Public entry point (called by workflow files via @dimsim/eval) ───────── /** - * Load a workflow module and run it to completion. Resolves with the - * result message (also sent over WS for the runner). + * Run a workflow object end-to-end. Workflow files do: + * + * import { runEval } from '@dimsim/eval'; + * await runEval({ scene, task, success, … }); + * + * That import resolves to public/_dimsim/eval-api.js which delegates to + * this method via `window.__dimsim.eval.runEval`. Result is both + * returned to the caller AND sent over WS as `{type:'evalResult'}` for + * the Deno runner. */ - async runEval(workflowUrl: string): Promise { + async runEval(workflow: EvalWorkflow): Promise { + if (!workflow || typeof workflow.success !== "function") { + const msg = "runEval(workflow) requires { scene, task, success() }"; + console.error(`[eval] ${msg}`); + return this._fail("", "", "", msg); + } + const tag = `${workflow.scene ?? "?"}/${workflow.task}`; if (this._activeUrl) { const err = `another eval is already running: ${this._activeUrl}`; console.warn(`[eval] ${err}`); - return this._fail(workflowUrl, "", "", err); - } - this._activeUrl = workflowUrl; - - let wf: EvalWorkflow; - try { - const cacheBust = `?t=${Date.now()}`; - const mod = await import(/* @vite-ignore */ workflowUrl + cacheBust); - wf = mod.default; - if (!wf || typeof wf.success !== "function") { - throw new Error("workflow module must default-export { scene, task, success() }"); - } - } catch (e: any) { - console.error(`[eval] failed to load ${workflowUrl}:`, e); - this._activeUrl = null; - return this._fail(workflowUrl, "", "", `load failed: ${e?.message ?? e}`); + return this._fail("", workflow.scene, workflow.task, err); } + this._activeUrl = tag; - console.log(`[eval] running: ${workflowUrl} — "${wf.task}"`); - this._showOverlay(wf.task, wf.timeoutSec ?? 120); + console.log(`[eval] running: ${tag}`); + this._showOverlay(workflow.task, workflow.timeoutSec ?? 120); const start = Date.now(); - const timeoutMs = (wf.timeoutSec ?? 120) * 1000; + const timeoutMs = (workflow.timeoutSec ?? 120) * 1000; const ctx = this._makeContext(); - if (wf.startPose) ctx.setAgentPose(wf.startPose); - if (wf.setup) { - try { await wf.setup(ctx); } + if (workflow.startPose) ctx.setAgentPose(workflow.startPose); + if (workflow.setup) { + try { await workflow.setup(ctx); } catch (e: any) { const reason = `setup() threw: ${e?.message ?? e}`; console.error(`[eval] ${reason}`); this._activeUrl = null; - return this._fail(workflowUrl, wf.scene, wf.task, reason, Date.now() - start); + return this._fail("", workflow.scene, workflow.task, reason, Date.now() - start); } } @@ -204,17 +223,16 @@ export class EvalHarness { const elapsed = Date.now() - start; let result: EvalSuccess; try { - result = wf.success(this._makeContext()); + result = workflow.success(this._makeContext()); } catch (e: any) { result = { passed: false, reason: `success() threw: ${e?.message ?? e}` }; } - if (result.passed) { - this._finish(workflowUrl, wf, true, result, elapsed, resolve); + this._finish(workflow, true, result, elapsed, resolve); return; } if (elapsed >= timeoutMs) { - this._finish(workflowUrl, wf, false, { passed: false, ...result, reason: result.reason ?? "timeout" }, elapsed, resolve); + this._finish(workflow, false, { passed: false, ...result, reason: result.reason ?? "timeout" }, elapsed, resolve); return; } setTimeout(tick, 250); @@ -253,13 +271,13 @@ export class EvalHarness { } _finish( - workflowUrl: string, wf: EvalWorkflow, passed: boolean, + wf: EvalWorkflow, passed: boolean, result: EvalSuccess, durationMs: number, resolve: (msg: EvalResultMsg) => void, ): void { const msg: EvalResultMsg = { type: "evalResult", - workflowUrl, + workflowUrl: "", scene: wf.scene, task: wf.task, passed, From 29c475d7682ef36aee5933b4e89724a339ff5f21 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 16:30:22 -0700 Subject: [PATCH 18/43] misc/DimSim: drop eval-api.js proxy; pin harness chunk so importmap targets it directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously workflow files imported `@dimsim/eval`, which the importmap aliased to a hand-written ESM proxy under public/_dimsim/. The proxy existed only to bridge between un-bundled user scripts and the bundled engine's hash-named harness chunk — it delegated to a window global (`window.__dimsim.eval.runEval`) after waiting on a custom DOM event. Fishy. Cleaner: tell Vite to pin the harness chunk's filename (`dist/assets/dimsim-eval.js`) and point the importmap straight at it. Now the workflow file and the engine import the *same module* — module identity is preserved by the browser's ESM loader — so a module-level singleton works. Changes: - vite.config.js: chunkFileNames pins src/evals/harness.ts → dimsim-eval.js - src/evals/harness.ts: adds setEvalHarness(h) + module-level runEval(workflow) that delegates to the registered singleton. - src/engine.js: calls setEvalHarness(evalHarness) after construction; drops the window.__dimsim.eval global + dispatchEvent. - index.html: importmap now points at /assets/dimsim-eval.js - public/_dimsim/eval-api.js: deleted, dir gone Workflow files are unchanged — still `import { runEval } from '@dimsim/eval'` followed by top-level await. Verified end-to-end headless: deno run -A --unstable-net dimos-cli/cli.ts \ eval --headless --scene apartment --workflow go-to-couch → loads apartment scene, dynamic-imports go-to-couch.js, runs setup, polls success every 250ms, fails on 30s timeout with a clean "3.313m to Modern L-shaped sectional (threshold 2m)" reason. --- misc/DimSim/index.html | 8 ++++-- misc/DimSim/public/_dimsim/eval-api.js | 31 --------------------- misc/DimSim/src/engine.js | 13 ++++----- misc/DimSim/src/evals/harness.ts | 38 ++++++++++++++++++++++++++ misc/DimSim/vite.config.js | 13 +++++++++ 5 files changed, 62 insertions(+), 41 deletions(-) delete mode 100644 misc/DimSim/public/_dimsim/eval-api.js diff --git a/misc/DimSim/index.html b/misc/DimSim/index.html index e422c18288..d8868619cf 100644 --- a/misc/DimSim/index.html +++ b/misc/DimSim/index.html @@ -9,10 +9,12 @@ + Vite pins the harness chunk (src/evals/harness.ts) to a stable + filename (see vite.config.js → chunkFileNames), so workflow files + get the same module the engine loaded — sharing the singleton + state registered via setEvalHarness(). --> diff --git a/misc/DimSim/public/_dimsim/eval-api.js b/misc/DimSim/public/_dimsim/eval-api.js deleted file mode 100644 index 70ac5f7af1..0000000000 --- a/misc/DimSim/public/_dimsim/eval-api.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @dimsim/eval — public ESM facade for eval workflows. - * - * Eval files under `scenes//evals/*.js` import `runEval` from here and - * call it directly: - * - * import { runEval } from '@dimsim/eval'; - * await runEval({ - * scene: 'apartment', - * task: 'Go to the couch', - * success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional' }), - * }); - * - * The import map in index.html aliases `@dimsim/eval` to this file. At - * runtime we wait for the engine to wire up `window.__dimsim.eval.runEval` - * (it dispatches a `dimsim-eval-ready` event when it's available) and then - * delegate the call. This indirection keeps the public surface decoupled - * from the bundled engine chunk's hashed filename. - */ - -async function _ready() { - if (window.__dimsim?.eval?.runEval) return; - await new Promise((resolve) => { - window.addEventListener('dimsim-eval-ready', resolve, { once: true }); - }); -} - -export async function runEval(workflow) { - await _ready(); - return window.__dimsim.eval.runEval(workflow); -} diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index bc95f44bc1..e3833dd39b 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7551,7 +7551,8 @@ if (dimosMode) { // Browser no longer needs to publish odom — server is authoritative. // Eval harness — scores objectDistance rubric when triggered by dimsim eval runner - const { EvalHarness } = await import("./evals/harness.ts"); + const harnessMod = await import("./evals/harness.ts"); + const { EvalHarness, setEvalHarness } = harnessMod; const channel = new URLSearchParams(location.search).get("channel") || undefined; const evalHarness = new EvalHarness({ bridge, @@ -7587,12 +7588,10 @@ if (dimosMode) { }, }); window.__evalHarness = evalHarness; - // Expose runEval(workflow) so workflow files imported from - // `@dimsim/eval` (via the import map → public/_dimsim/eval-api.js) - // can drive evaluations directly with top-level await. - window.__dimsim = window.__dimsim || {}; - window.__dimsim.eval = { runEval: (wf) => evalHarness.runEval(wf) }; - window.dispatchEvent(new Event('dimsim-eval-ready')); + // Register the singleton so workflow files importing `runEval` from + // `@dimsim/eval` (importmap → dist/assets/dimsim-eval.js → this same + // module) get a working runEval. + setEvalHarness(evalHarness); // Scene editor — script execution engine for sim editing (exec_js API) const { SceneEditor } = await import("./sceneEditor.ts"); diff --git a/misc/DimSim/src/evals/harness.ts b/misc/DimSim/src/evals/harness.ts index 7fbb218027..ae18594c0e 100644 --- a/misc/DimSim/src/evals/harness.ts +++ b/misc/DimSim/src/evals/harness.ts @@ -85,6 +85,44 @@ declare global { interface Window { __dimosAgent?: any; } } +// ── Singleton registration ────────────────────────────────────────────────── +// +// Workflow files import `runEval` from `@dimsim/eval`. The importmap in +// index.html points that bare specifier at this very chunk's bundled +// filename (pinned by vite.config.js → `dist/assets/dimsim-eval.js`), so +// the workflow ends up importing this same module — which means it sees +// the `_instance` set below by engine.js after EvalHarness construction. + +let _instance: EvalHarness | null = null; +let _readyResolvers: Array<() => void> = []; + +/** engine.js calls this once the harness is wired up. */ +export function setEvalHarness(h: EvalHarness): void { + _instance = h; + const r = _readyResolvers; + _readyResolvers = []; + for (const fn of r) fn(); +} + +async function _waitForInstance(): Promise { + if (_instance) return _instance; + await new Promise((resolve) => _readyResolvers.push(resolve)); + return _instance!; +} + +/** + * Public entry — what workflow files call after importing from + * `@dimsim/eval`. Resolves when the workflow finishes (passed, failed, + * or timed out); also sends a `{type:'evalResult'}` WS message for the + * Deno runner along the way. + */ +export async function runEval(workflow: EvalWorkflow): Promise { + const h = await _waitForInstance(); + return h.runEval(workflow); +} + +// ──────────────────────────────────────────────────────────────────────────── + export class EvalHarness { bridge: DimosBridge; getSceneState: () => SceneState; diff --git a/misc/DimSim/vite.config.js b/misc/DimSim/vite.config.js index 4d21fd3a4b..b95cdf5c10 100644 --- a/misc/DimSim/vite.config.js +++ b/misc/DimSim/vite.config.js @@ -9,6 +9,19 @@ export default defineConfig({ assetsInlineLimit: 0, rollupOptions: { external: [/^https:\/\/esm\.sh\//], + output: { + // Eval workflow files under scenes/*/evals/*.js import runEval from + // '@dimsim/eval' via the importmap in index.html. That map needs to + // point at a *stable* URL, so pin the harness chunk's filename here + // (it's the public ESM surface for evals). Everything else keeps + // its content-hashed name. + chunkFileNames(chunk) { + if (chunk.facadeModuleId?.endsWith("/src/evals/harness.ts")) { + return "assets/dimsim-eval.js"; + } + return "assets/[name]-[hash].js"; + }, + }, }, }, server: { From 04ed6e805127cafd4d9e7161e2162250830c71d9 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 17:56:00 -0700 Subject: [PATCH 19/43] misc/DimSim: positional workflow arg for `dimsim eval` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dimsim eval ` is now shorthand for `dimsim eval --workflow --connect` — the common dev-loop case where the sim is already open and you just want to run one eval against it. dimsim eval go-to-couch # any scene that has the workflow dimsim eval apartment/go-to-couch # scene-qualified Auto-defaults to --connect because spinning up a fresh headless bridge for a one-off invocation is rarely the right move during dev — that mode is for CI and is still reachable as `dimsim eval --headless …`. The runner / harness wiring is unchanged; this is purely a cli arg shape change. To install the cli on PATH (one-time): cd misc/DimSim/dimos-cli deno install -gAf --unstable-net --name=dimsim --config=./deno.json ./cli.ts --- misc/DimSim/dimos-cli/cli.ts | 48 +++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/misc/DimSim/dimos-cli/cli.ts b/misc/DimSim/dimos-cli/cli.ts index 84411db8d9..59b2ff41d6 100644 --- a/misc/DimSim/dimos-cli/cli.ts +++ b/misc/DimSim/dimos-cli/cli.ts @@ -5,6 +5,7 @@ * * Usage: * dimsim dev [--scene ] [--port ] Dev server + browser + * dimsim eval Run one workflow (auto --connect) * dimsim eval [--headless] [--parallel N] [--render gpu] Headless CI evals * dimsim eval list List eval workflows * dimsim agent [--nav-only] dimos Python agent @@ -111,6 +112,7 @@ DimSim CLI — 3D simulation + eval harness for dimos Commands: dimsim dev [options] Dev server (open browser, optional eval) dimsim eval list List installed eval workflows + dimsim eval Run one workflow against an already-running bridge dimsim eval [options] Run eval workflows (headless CI) dimsim agent [options] Launch dimos Python agent @@ -326,19 +328,34 @@ async function main() { // ── Eval ──────────────────────────────────────────────────────────── if (subcommand === "eval") { - const connectMode = opts.connect === true; + // Positional workflow: `dimsim eval go-to-tv` is shorthand for + // `dimsim eval --workflow go-to-tv --connect`. Accepts either bare + // workflow name ("go-to-tv") or scene-qualified ("apartment/go-to-tv"). + const positional = Deno.args[1] && !Deno.args[1].startsWith("--") ? Deno.args[1] : null; + let posScene: string | undefined; + let posWorkflow: string | undefined; + if (positional) { + const slash = positional.indexOf("/"); + if (slash !== -1) { + posScene = positional.slice(0, slash); + posWorkflow = positional.slice(slash + 1); + } else { + posWorkflow = positional; + } + } + // If a workflow was given positionally, default to --connect. Spinning up + // a fresh headless bridge for a one-off run during dev is rarely what you + // want; the common case is "the sim is already open, run this eval in it". + const connectMode = opts.connect === true || positional !== null; const outputFormat = (opts.output as string) === "junit" ? "junit" : "json"; const wsUrl = `ws://localhost:${port}`; + const filterScene = posScene ?? (opts.scene as string) ?? (opts.env as string); + const filterWorkflow = posWorkflow ?? (opts.workflow as string); // --connect mode: just run the runner against an existing bridge if (connectMode) { console.log(`[dimsim] Connecting to existing bridge at ${wsUrl}…`); - const results = await runEvals({ - wsUrl, - scenesRoot: SCENES_DIR, - filterScene: (opts.scene as string) || (opts.env as string), - filterWorkflow: opts.workflow as string, - }); + const results = await runEvals({ wsUrl, scenesRoot: SCENES_DIR, filterScene, filterWorkflow }); if (outputFormat === "junit") console.log(toJunitXml(results)); const passed = results.filter((r) => r.passed).length; const failed = results.length - passed; @@ -356,9 +373,7 @@ async function main() { if (headless && parallel > 1) { const allWorkflows = collectWorkflows({ - scenesRoot: SCENES_DIR, - filterScene: (opts.scene as string) || (opts.env as string), - filterWorkflow: opts.workflow as string, + scenesRoot: SCENES_DIR, filterScene, filterWorkflow, }); if (allWorkflows.length === 0) { console.log("[dimsim] No workflows match filter criteria."); @@ -375,11 +390,9 @@ async function main() { await new Promise((r) => setTimeout(r, 2000)); const allResults = await runEvalsMultiPage({ - wsUrl, - scenesRoot: SCENES_DIR, + wsUrl, scenesRoot: SCENES_DIR, channels: instance.channels, - filterScene: (opts.scene as string) || (opts.env as string), - filterWorkflow: opts.workflow as string, + filterScene, filterWorkflow, }); await instance.close(); @@ -404,12 +417,7 @@ async function main() { const instance = await launchHeadless({ url, timeout, render }); await new Promise((r) => setTimeout(r, 3000)); - const results = await runEvals({ - wsUrl, - scenesRoot: SCENES_DIR, - filterScene: (opts.scene as string) || (opts.env as string), - filterWorkflow: opts.workflow as string, - }); + const results = await runEvals({ wsUrl, scenesRoot: SCENES_DIR, filterScene, filterWorkflow }); if (outputFormat === "junit") console.log(toJunitXml(results)); await instance.close(); From f2c572cacfba9d8bf161a1b79f64468d9b785d61 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 17:59:48 -0700 Subject: [PATCH 20/43] misc/DimSim: workflow files are directly executable via `deno run` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workflow file under scenes//evals/.js can now be run as a Deno program, with no shape change to the file itself: deno run -A misc/DimSim/scenes/apartment/evals/go-to-couch.js The same import + same call: import { runEval } from '@dimsim/eval'; await runEval({ scene, task, success, … }); …now resolves differently depending on runtime: - Browser → importmap in index.html → /assets/dimsim-eval.js (bundled EvalHarness chunk) → runs the eval in-place against the real THREE.js scene, agent, Rapier. - Deno → scenes/deno.json → dimos-cli/eval/deno-client.ts → opens a control WS to ws://localhost:8090, sends {type:'runEval', workflowUrl}, awaits evalResult, exits. The browser is what actually re-imports the file and executes setup/success — Deno is just a dispatcher. Two new files: - dimos-cli/eval/deno-client.ts — the Deno runEval; reads Deno.mainModule to figure out the workflow URL, connects to whatever bridge is up on DIMSIM_PORT (default 8090). - scenes/deno.json — scoped import map for `@dimsim/eval` so anything under scenes/ resolves the bare specifier correctly. Verified end-to-end against a headless bridge: deno run -A scenes/apartment/evals/go-to-couch.js [eval] dispatching /scenes/apartment/evals/go-to-couch.js → ws://localhost:8090/?ch=control [eval] task: Go to the couch [eval] PASS (277ms): 1.693m to "Modern L-shaped sectional" (threshold 2m) `dimsim eval ` keeps working — both shortcuts dispatch to the same EvalHarness, just over different framing. --- misc/DimSim/dimos-cli/eval/deno-client.ts | 107 ++++++++++++++++++++++ misc/DimSim/scenes/deno.json | 6 ++ misc/DimSim/scenes/deno.lock | 23 +++++ 3 files changed, 136 insertions(+) create mode 100644 misc/DimSim/dimos-cli/eval/deno-client.ts create mode 100644 misc/DimSim/scenes/deno.json create mode 100644 misc/DimSim/scenes/deno.lock diff --git a/misc/DimSim/dimos-cli/eval/deno-client.ts b/misc/DimSim/dimos-cli/eval/deno-client.ts new file mode 100644 index 0000000000..b7224ba0a7 --- /dev/null +++ b/misc/DimSim/dimos-cli/eval/deno-client.ts @@ -0,0 +1,107 @@ +/** + * Deno-side `@dimsim/eval` runEval — dispatch a workflow file to the + * browser via the bridge instead of running it locally. + * + * A workflow file under scenes//evals/.js does: + * + * import { runEval } from '@dimsim/eval'; + * await runEval({ scene, task, success, ... }); + * + * In the browser, that import resolves (via the index.html importmap) + * to the bundled EvalHarness chunk and runs the eval in-place. + * + * In Deno, scenes/deno.json maps `@dimsim/eval` here. We don't have a + * THREE.js scene, agent, or Rapier — we just open a control WebSocket + * to a running bridge, ship `{type:'runEval', workflowUrl}`, and wait + * for `{type:'evalResult'}`. The browser imports the same workflow + * file URL and runs the success/setup callbacks for real. + * + * Net effect: `deno run -A scenes//evals/.js` is a one-liner + * shortcut for `dimsim eval /`, both end at the same + * EvalHarness in the open browser. + */ + +import { fromFileUrl } from "@std/path"; + +interface DenoEvalResult { + type: "evalResult"; + workflowUrl: string; + scene: string; + task: string; + passed: boolean; + reason?: string; + score?: number; + durationMs: number; +} + +/** Find the "/scenes/..." segment in the entry-point file path. */ +function _resolveWorkflowUrl(): string { + const main = Deno.mainModule; + if (!main.startsWith("file://")) { + throw new Error( + `@dimsim/eval: can only infer workflow URL from a 'deno run ' invocation, got ${main}`, + ); + } + const abs = fromFileUrl(main); + const i = abs.indexOf("/scenes/"); + if (i === -1) { + throw new Error( + `@dimsim/eval: workflow file must live under a 'scenes/' directory; got ${abs}`, + ); + } + return abs.slice(i); // e.g. "/scenes/apartment/evals/go-to-couch.js" +} + +/** Open the control WebSocket, race resolve / error / 5s timeout. */ +function _connect(wsUrl: string): Promise { + return new Promise((resolve, reject) => { + const ws = new WebSocket(wsUrl); + const t = setTimeout(() => reject(new Error(`@dimsim/eval: timed out connecting to ${wsUrl}`)), 5000); + ws.addEventListener("open", () => { clearTimeout(t); resolve(ws); }, { once: true }); + ws.addEventListener("error", (e) => { clearTimeout(t); reject(e); }, { once: true }); + }); +} + +/** + * Public entry — same name + same call shape as the browser export, so + * workflow files don't change between runtimes. The `workflow` object + * is forwarded only as a logging convenience here; the browser is what + * actually runs `setup` / `success` (it re-imports the file from URL). + */ +export async function runEval(workflow: { scene?: string; task?: string }): Promise { + const workflowUrl = _resolveWorkflowUrl(); + const port = parseInt(Deno.env.get("DIMSIM_PORT") || "8090"); + const wsUrl = `ws://localhost:${port}/?ch=control`; + + console.log(`[eval] dispatching ${workflowUrl} → ${wsUrl}`); + if (workflow?.task) console.log(`[eval] task: ${workflow.task}`); + + let ws: WebSocket; + try { + ws = await _connect(wsUrl); + } catch (e: any) { + console.error(`[eval] failed to connect to bridge — is dimsim running? (${e?.message ?? e})`); + Deno.exit(2); + } + + try { + const result = await new Promise((resolve) => { + ws.addEventListener("message", (event) => { + if (typeof event.data !== "string") return; + let msg: any; + try { msg = JSON.parse(event.data); } catch { return; } + if (msg.type !== "evalResult") return; + if (msg.workflowUrl && msg.workflowUrl !== workflowUrl) return; + resolve(msg as DenoEvalResult); + }); + ws.send(JSON.stringify({ type: "runEval", workflowUrl })); + }); + + const tag = result.passed ? "\x1b[32mPASS\x1b[0m" : "\x1b[31mFAIL\x1b[0m"; + console.log(`[eval] ${tag} (${result.durationMs}ms): ${result.reason ?? ""}`); + if (!result.passed) Deno.exit(1); + return result; + } finally { + try { ws.close(); } catch { /* ignore */ } + } +} diff --git a/misc/DimSim/scenes/deno.json b/misc/DimSim/scenes/deno.json new file mode 100644 index 0000000000..74ddc70d9f --- /dev/null +++ b/misc/DimSim/scenes/deno.json @@ -0,0 +1,6 @@ +{ + "imports": { + "@dimsim/eval": "../dimos-cli/eval/deno-client.ts", + "@std/path": "jsr:@std/path@^1" + } +} diff --git a/misc/DimSim/scenes/deno.lock b/misc/DimSim/scenes/deno.lock new file mode 100644 index 0000000000..29ad14327a --- /dev/null +++ b/misc/DimSim/scenes/deno.lock @@ -0,0 +1,23 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/path@1": "1.1.4" + }, + "jsr": { + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/path@1" + ] + } +} From d3a0073e6e2ade7a0e66ff01e848d78411c56bf1 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:05:57 -0700 Subject: [PATCH 21/43] =?UTF-8?q?misc/DimSim:=20collapse=20eval=20folders?= =?UTF-8?q?=20+=20rename=20dimos-cli/=20=E2=86=92=20cli/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two restructures asked for in review: 1. The eval system was split across two folders (src/evals/ for the browser-side harness+rubrics, dimos-cli/eval/ for the Deno runner+ client) which made it hard to find anything eval-related. Both move to a single top-level evals/ folder; filenames make the runtime obvious (`harness`+`rubrics` are browser, `runner`+`deno-client` are Deno). 2. `dimos-cli/` was a misnomer — DimSim already lives inside dimos, so "dimos-cli" inside misc/DimSim is doubled up. Renamed to cli/. New tree: misc/DimSim/ src/ engine.js, AiAvatar.js, main.js, style.css, bridge.ts, sceneApi.ts, sceneEditor.ts cli/ cli.ts, deno.json, deno.lock, bridge/, headless/, vendor/lcm/, test/, agent.py, README.md evals/ harness.ts, rubrics.ts (browser), runner.ts, deno-client.ts (Deno), deno.json (LSP hints) scenes/ apartment/{index.js,data/,textures/,evals/}, empty/, warehouse/, deno.json public/, index.html, package.json, vite.config.js, … Touched files (paths only — no logic changes): - src/engine.js: import "../evals/harness.ts" - cli/cli.ts: import "../evals/runner.ts" - scenes/deno.json: @dimsim/eval → "../evals/deno-client.ts" - evals/deno-client.ts: /// for IDE - evals/deno.json: scoped import map for @std/path - vite.config.js: chunkFileNames matches "/evals/harness.ts" - dimos/simulation/dimsim/dimsim_process.py: `dimos-cli` → `cli` - .github/workflows/dimsim-check.yml: `cd dimos-cli` → `cd cli` Re-install dimsim global (path of cli.ts changed): cd misc/DimSim/cli deno install -gAf --unstable-net --name=dimsim --config=./deno.json ./cli.ts Vite build still emits assets/dimsim-eval.js (pinned), and the headed + headless + direct-deno-run eval paths all keep working. --- .github/workflows/dimsim-check.yml | 2 +- dimos/simulation/dimsim/dimsim_process.py | 6 +++--- misc/DimSim/{dimos-cli => cli}/README.md | 0 misc/DimSim/{dimos-cli => cli}/agent.py | 4 ++-- misc/DimSim/{dimos-cli => cli}/bridge/lidar.ts | 0 misc/DimSim/{dimos-cli => cli}/bridge/physics.ts | 0 misc/DimSim/{dimos-cli => cli}/bridge/server.ts | 0 misc/DimSim/{dimos-cli => cli}/cli.ts | 2 +- misc/DimSim/{dimos-cli => cli}/deno.json | 0 misc/DimSim/{dimos-cli => cli}/deno.lock | 0 misc/DimSim/{dimos-cli => cli}/headless/launcher.ts | 0 misc/DimSim/{dimos-cli => cli}/test/dimos_integration.py | 4 ++-- misc/DimSim/{dimos-cli => cli}/vendor/lcm/lcm.ts | 0 misc/DimSim/{dimos-cli => cli}/vendor/lcm/mod.ts | 0 misc/DimSim/{dimos-cli => cli}/vendor/lcm/transport.ts | 0 misc/DimSim/{dimos-cli => cli}/vendor/lcm/types.ts | 0 misc/DimSim/{dimos-cli => cli}/vendor/lcm/url.ts | 0 misc/DimSim/{dimos-cli/eval => evals}/deno-client.ts | 1 + misc/DimSim/evals/deno.json | 5 +++++ misc/DimSim/{src => }/evals/harness.ts | 0 misc/DimSim/{src => }/evals/rubrics.ts | 0 misc/DimSim/{dimos-cli/eval => evals}/runner.ts | 0 misc/DimSim/scenes/deno.json | 2 +- misc/DimSim/src/engine.js | 2 +- misc/DimSim/vite.config.js | 2 +- 25 files changed, 18 insertions(+), 12 deletions(-) rename misc/DimSim/{dimos-cli => cli}/README.md (100%) rename misc/DimSim/{dimos-cli => cli}/agent.py (95%) rename misc/DimSim/{dimos-cli => cli}/bridge/lidar.ts (100%) rename misc/DimSim/{dimos-cli => cli}/bridge/physics.ts (100%) rename misc/DimSim/{dimos-cli => cli}/bridge/server.ts (100%) rename misc/DimSim/{dimos-cli => cli}/cli.ts (99%) rename misc/DimSim/{dimos-cli => cli}/deno.json (100%) rename misc/DimSim/{dimos-cli => cli}/deno.lock (100%) rename misc/DimSim/{dimos-cli => cli}/headless/launcher.ts (100%) rename misc/DimSim/{dimos-cli => cli}/test/dimos_integration.py (98%) rename misc/DimSim/{dimos-cli => cli}/vendor/lcm/lcm.ts (100%) rename misc/DimSim/{dimos-cli => cli}/vendor/lcm/mod.ts (100%) rename misc/DimSim/{dimos-cli => cli}/vendor/lcm/transport.ts (100%) rename misc/DimSim/{dimos-cli => cli}/vendor/lcm/types.ts (100%) rename misc/DimSim/{dimos-cli => cli}/vendor/lcm/url.ts (100%) rename misc/DimSim/{dimos-cli/eval => evals}/deno-client.ts (99%) create mode 100644 misc/DimSim/evals/deno.json rename misc/DimSim/{src => }/evals/harness.ts (100%) rename misc/DimSim/{src => }/evals/rubrics.ts (100%) rename misc/DimSim/{dimos-cli/eval => evals}/runner.ts (100%) diff --git a/.github/workflows/dimsim-check.yml b/.github/workflows/dimsim-check.yml index 77419152ab..821306cccf 100644 --- a/.github/workflows/dimsim-check.yml +++ b/.github/workflows/dimsim-check.yml @@ -42,4 +42,4 @@ jobs: run: npm run build - name: Type-check CLI - run: cd dimos-cli && deno check cli.ts + run: cd cli && deno check cli.ts diff --git a/dimos/simulation/dimsim/dimsim_process.py b/dimos/simulation/dimsim/dimsim_process.py index 25b0403389..beedfc4533 100644 --- a/dimos/simulation/dimsim/dimsim_process.py +++ b/dimos/simulation/dimsim/dimsim_process.py @@ -146,15 +146,15 @@ def _resolve_dimsim_dir() -> Path: path = dimos_root.parent / "DimSim" else: path = Path(local).expanduser().resolve() - if not (path / "dimos-cli" / "cli.ts").exists(): + if not (path / "cli" / "cli.ts").exists(): raise RuntimeError( f"DIMSIM_LOCAL={local} resolved to {path}, but " - f"{path}/dimos-cli/cli.ts does not exist" + f"{path}/cli/cli.ts does not exist" ) logger.info(f"Using local DimSim from {path}") return path def _deno_cmd(deno_path: str, repo_dir: Path) -> list[str]: - cli_ts = repo_dir / "dimos-cli" / "cli.ts" + cli_ts = repo_dir / "cli" / "cli.ts" return [deno_path, "run", "--allow-all", "--unstable-net", str(cli_ts)] diff --git a/misc/DimSim/dimos-cli/README.md b/misc/DimSim/cli/README.md similarity index 100% rename from misc/DimSim/dimos-cli/README.md rename to misc/DimSim/cli/README.md diff --git a/misc/DimSim/dimos-cli/agent.py b/misc/DimSim/cli/agent.py similarity index 95% rename from misc/DimSim/dimos-cli/agent.py rename to misc/DimSim/cli/agent.py index 0c22f4543c..3481eb138b 100644 --- a/misc/DimSim/dimos-cli/agent.py +++ b/misc/DimSim/cli/agent.py @@ -9,8 +9,8 @@ This script runs the dimos brain that processes those sensors and sends commands. Usage (run with dimos venv): - ../dimos/.venv/bin/python dimos-cli/agent.py - ../dimos/.venv/bin/python dimos-cli/agent.py --nav-only # no LLM agent, just exploration + ../dimos/.venv/bin/python cli/agent.py + ../dimos/.venv/bin/python cli/agent.py --nav-only # no LLM agent, just exploration """ import argparse diff --git a/misc/DimSim/dimos-cli/bridge/lidar.ts b/misc/DimSim/cli/bridge/lidar.ts similarity index 100% rename from misc/DimSim/dimos-cli/bridge/lidar.ts rename to misc/DimSim/cli/bridge/lidar.ts diff --git a/misc/DimSim/dimos-cli/bridge/physics.ts b/misc/DimSim/cli/bridge/physics.ts similarity index 100% rename from misc/DimSim/dimos-cli/bridge/physics.ts rename to misc/DimSim/cli/bridge/physics.ts diff --git a/misc/DimSim/dimos-cli/bridge/server.ts b/misc/DimSim/cli/bridge/server.ts similarity index 100% rename from misc/DimSim/dimos-cli/bridge/server.ts rename to misc/DimSim/cli/bridge/server.ts diff --git a/misc/DimSim/dimos-cli/cli.ts b/misc/DimSim/cli/cli.ts similarity index 99% rename from misc/DimSim/dimos-cli/cli.ts rename to misc/DimSim/cli/cli.ts index 59b2ff41d6..2496437cca 100644 --- a/misc/DimSim/dimos-cli/cli.ts +++ b/misc/DimSim/cli/cli.ts @@ -14,7 +14,7 @@ import { resolve, dirname, fromFileUrl } from "@std/path"; import { startBridgeServer } from "./bridge/server.ts"; import { launchHeadless, launchMultiPage, type RenderMode } from "./headless/launcher.ts"; -import { runEvals, runEvalsMultiPage, collectWorkflows, toJunitXml, type EvalResult } from "./eval/runner.ts"; +import { runEvals, runEvalsMultiPage, collectWorkflows, toJunitXml, type EvalResult } from "../evals/runner.ts"; const CLI_DIR = dirname(fromFileUrl(import.meta.url)); const PROJECT_DIR = resolve(CLI_DIR, ".."); diff --git a/misc/DimSim/dimos-cli/deno.json b/misc/DimSim/cli/deno.json similarity index 100% rename from misc/DimSim/dimos-cli/deno.json rename to misc/DimSim/cli/deno.json diff --git a/misc/DimSim/dimos-cli/deno.lock b/misc/DimSim/cli/deno.lock similarity index 100% rename from misc/DimSim/dimos-cli/deno.lock rename to misc/DimSim/cli/deno.lock diff --git a/misc/DimSim/dimos-cli/headless/launcher.ts b/misc/DimSim/cli/headless/launcher.ts similarity index 100% rename from misc/DimSim/dimos-cli/headless/launcher.ts rename to misc/DimSim/cli/headless/launcher.ts diff --git a/misc/DimSim/dimos-cli/test/dimos_integration.py b/misc/DimSim/cli/test/dimos_integration.py similarity index 98% rename from misc/DimSim/dimos-cli/test/dimos_integration.py rename to misc/DimSim/cli/test/dimos_integration.py index 2c0123f269..51b07f06c6 100755 --- a/misc/DimSim/dimos-cli/test/dimos_integration.py +++ b/misc/DimSim/cli/test/dimos_integration.py @@ -17,10 +17,10 @@ Prerequisites: 1. Start DimSim bridge: - ~/.deno/bin/deno run --allow-all --unstable-net dimos-cli/cli.ts dev + ~/.deno/bin/deno run --allow-all --unstable-net cli/cli.ts dev 2. Open http://localhost:8090 in Chrome (scene must load) 3. Run this script from the dimos venv: - /path/to/dimos/.venv/bin/python dimos-cli/test/dimos_integration.py + /path/to/dimos/.venv/bin/python cli/test/dimos_integration.py Options: --timeout N Timeout in seconds (default: 30) diff --git a/misc/DimSim/dimos-cli/vendor/lcm/lcm.ts b/misc/DimSim/cli/vendor/lcm/lcm.ts similarity index 100% rename from misc/DimSim/dimos-cli/vendor/lcm/lcm.ts rename to misc/DimSim/cli/vendor/lcm/lcm.ts diff --git a/misc/DimSim/dimos-cli/vendor/lcm/mod.ts b/misc/DimSim/cli/vendor/lcm/mod.ts similarity index 100% rename from misc/DimSim/dimos-cli/vendor/lcm/mod.ts rename to misc/DimSim/cli/vendor/lcm/mod.ts diff --git a/misc/DimSim/dimos-cli/vendor/lcm/transport.ts b/misc/DimSim/cli/vendor/lcm/transport.ts similarity index 100% rename from misc/DimSim/dimos-cli/vendor/lcm/transport.ts rename to misc/DimSim/cli/vendor/lcm/transport.ts diff --git a/misc/DimSim/dimos-cli/vendor/lcm/types.ts b/misc/DimSim/cli/vendor/lcm/types.ts similarity index 100% rename from misc/DimSim/dimos-cli/vendor/lcm/types.ts rename to misc/DimSim/cli/vendor/lcm/types.ts diff --git a/misc/DimSim/dimos-cli/vendor/lcm/url.ts b/misc/DimSim/cli/vendor/lcm/url.ts similarity index 100% rename from misc/DimSim/dimos-cli/vendor/lcm/url.ts rename to misc/DimSim/cli/vendor/lcm/url.ts diff --git a/misc/DimSim/dimos-cli/eval/deno-client.ts b/misc/DimSim/evals/deno-client.ts similarity index 99% rename from misc/DimSim/dimos-cli/eval/deno-client.ts rename to misc/DimSim/evals/deno-client.ts index b7224ba0a7..4bf67d1563 100644 --- a/misc/DimSim/dimos-cli/eval/deno-client.ts +++ b/misc/DimSim/evals/deno-client.ts @@ -1,3 +1,4 @@ +/// /** * Deno-side `@dimsim/eval` runEval — dispatch a workflow file to the * browser via the bridge instead of running it locally. diff --git a/misc/DimSim/evals/deno.json b/misc/DimSim/evals/deno.json new file mode 100644 index 0000000000..8c12c8280f --- /dev/null +++ b/misc/DimSim/evals/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "@std/path": "jsr:@std/path@^1" + } +} diff --git a/misc/DimSim/src/evals/harness.ts b/misc/DimSim/evals/harness.ts similarity index 100% rename from misc/DimSim/src/evals/harness.ts rename to misc/DimSim/evals/harness.ts diff --git a/misc/DimSim/src/evals/rubrics.ts b/misc/DimSim/evals/rubrics.ts similarity index 100% rename from misc/DimSim/src/evals/rubrics.ts rename to misc/DimSim/evals/rubrics.ts diff --git a/misc/DimSim/dimos-cli/eval/runner.ts b/misc/DimSim/evals/runner.ts similarity index 100% rename from misc/DimSim/dimos-cli/eval/runner.ts rename to misc/DimSim/evals/runner.ts diff --git a/misc/DimSim/scenes/deno.json b/misc/DimSim/scenes/deno.json index 74ddc70d9f..8f291d84e2 100644 --- a/misc/DimSim/scenes/deno.json +++ b/misc/DimSim/scenes/deno.json @@ -1,6 +1,6 @@ { "imports": { - "@dimsim/eval": "../dimos-cli/eval/deno-client.ts", + "@dimsim/eval": "../evals/deno-client.ts", "@std/path": "jsr:@std/path@^1" } } diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index e3833dd39b..ef6ff87575 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7551,7 +7551,7 @@ if (dimosMode) { // Browser no longer needs to publish odom — server is authoritative. // Eval harness — scores objectDistance rubric when triggered by dimsim eval runner - const harnessMod = await import("./evals/harness.ts"); + const harnessMod = await import("../evals/harness.ts"); const { EvalHarness, setEvalHarness } = harnessMod; const channel = new URLSearchParams(location.search).get("channel") || undefined; const evalHarness = new EvalHarness({ diff --git a/misc/DimSim/vite.config.js b/misc/DimSim/vite.config.js index b95cdf5c10..61760f311f 100644 --- a/misc/DimSim/vite.config.js +++ b/misc/DimSim/vite.config.js @@ -16,7 +16,7 @@ export default defineConfig({ // (it's the public ESM surface for evals). Everything else keeps // its content-hashed name. chunkFileNames(chunk) { - if (chunk.facadeModuleId?.endsWith("/src/evals/harness.ts")) { + if (chunk.facadeModuleId?.endsWith("/evals/harness.ts")) { return "assets/dimsim-eval.js"; } return "assets/[name]-[hash].js"; From d646ac05dc28e115775fa6a45d74ccedd0f16a8e Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:12:47 -0700 Subject: [PATCH 22/43] misc/DimSim: cleanup + write proper docs for the new setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop: - server.js (legacy Express+OpenAI VLM proxy — paired with the in-browser VLM stack we already deleted) - update-sims.sh (regenerated a manifest for public/sims/, which is gone) - scripts/{apt_to_single_glb,decompose_apt,decompose_objects_to_glb}.py (old GLB-decomposition flow, superseded by extract_apt_to_js.py) - scripts/package-release.sh (binary-release packaging — not used in the vendored-in-dimos model) - deno.lock at top level (cli/ has its own deno.lock for the CLI; top-level was stale) - docs/sdk-design.md (described an early design — no longer accurate) - cli/README.md (described JSR install + `dimsim setup` flow — dead) package.json: - Drop dead scripts (server / sync / parity:check / update-sims) and the dimos:* entries that pointed at the old dimos-cli/ path. - Drop runtime deps that only server.js used (express, cors, openai). - Now: vite + three + rapier + spark — that's it. - npm install needs --legacy-peer-deps because spark@latest expects three@^0.180 but the engine pins three@0.168. Stable enough for daily work; we'll bump three together with spark in a follow-up. README.md: rewritten for the current layout (was still describing the standalone Spark/SimStudio era with VLM backend on :8000). New docs/: - getting-started.md 5-minute tour + the two run modes + cheatsheet - scenes.md authoring scenes (Three.js dev cycle, api args, physics colliders, interactivity limitation) - evals.md authoring eval workflows + the three ways to run + the dual-runtime `@dimsim/eval` story - architecture.md full file-tree + dimos↔bridge↔browser data flow + key contracts (WS channels, LCM topics, scene/eval module shapes) Vite build still emits dist/assets/dimsim-eval.js (pinned), all the chunks ship as expected. --- misc/DimSim/README.md | 68 +- misc/DimSim/cli/README.md | 58 - misc/DimSim/deno.lock | 122 -- misc/DimSim/docs/architecture.md | 118 ++ misc/DimSim/docs/evals.md | 109 ++ misc/DimSim/docs/getting-started.md | 103 ++ misc/DimSim/docs/scenes.md | 86 ++ misc/DimSim/docs/sdk-design.md | 253 ---- misc/DimSim/package-lock.json | 1336 ++--------------- misc/DimSim/package.json | 16 +- misc/DimSim/scripts/apt_to_single_glb.py | 91 -- misc/DimSim/scripts/decompose_apt.py | 454 ------ .../scripts/decompose_objects_to_glb.py | 300 ---- misc/DimSim/scripts/package-release.sh | 45 - misc/DimSim/server.js | 174 --- misc/DimSim/update-sims.sh | 16 - 16 files changed, 572 insertions(+), 2777 deletions(-) delete mode 100644 misc/DimSim/cli/README.md delete mode 100644 misc/DimSim/deno.lock create mode 100644 misc/DimSim/docs/architecture.md create mode 100644 misc/DimSim/docs/evals.md create mode 100644 misc/DimSim/docs/getting-started.md create mode 100644 misc/DimSim/docs/scenes.md delete mode 100644 misc/DimSim/docs/sdk-design.md delete mode 100644 misc/DimSim/scripts/apt_to_single_glb.py delete mode 100644 misc/DimSim/scripts/decompose_apt.py delete mode 100644 misc/DimSim/scripts/decompose_objects_to_glb.py delete mode 100755 misc/DimSim/scripts/package-release.sh delete mode 100644 misc/DimSim/server.js delete mode 100644 misc/DimSim/update-sims.sh diff --git a/misc/DimSim/README.md b/misc/DimSim/README.md index 4df77dd82e..0644a28022 100644 --- a/misc/DimSim/README.md +++ b/misc/DimSim/README.md @@ -1,55 +1,55 @@ # DimSim -Standalone 3D simulation runner for SimStudio scenes. Load a scene, spawn AI agents, run tasks — with full sensor support (RGB-D, LiDAR). +Browser-based 3D simulator (Three.js + Rapier) plus a Deno bridge that talks LCM/WS to [dimos](https://github.com/dimensionalOS/dimos). Lives inside dimos as `misc/DimSim/`. -## Setup - -```bash -npm install # installs everything (frontend + backend) +``` +src/ — browser engine (vite-bundled) +cli/ — Deno CLI + bridge server + headless launcher + LCM vendor +evals/ — eval harness (browser) + runner (Deno) + rubrics +scenes/ — user-authored scenes (JS) + per-scene eval workflows +public/ — static assets (agent GLB, logo) +docs/ — guides ``` ## Run -Terminal 1: -```bash -npm run server # Node.js VLM backend on :8000 -``` +dimsim is launched by dimos directly when you pick `--simulation dimsim`: -Terminal 2: ```bash -npm run dev # Frontend on :5173 +cd +.venv/bin/dimos --simulation dimsim --dimsim-scene=apartment run unitree-go2-agentic ``` -## Architecture +On first run, `cli/cli.ts` will build `dist/` via Vite (dimsim ships its frontend as source — Deno+Vite materializes it in ~20s). -``` -DimSim/ -├── index.html ← Sim-mode UI (scene dropdown + full sensor controls) -├── server.js ← VLM backend (Express + OpenAI SDK) -├── src/ -│ ├── main.js ← Entry point (imports engine.js) -│ ├── engine.js ← Full SimStudio engine (synced via copy-sources.sh) -│ ├── style.css ← Synced from SimStudio -│ ├── AiAvatar.js ← Agent class (synced) -│ └── ai/ ← VLM modules (synced) -├── public/ -│ ├── sims/ ← Scene JSON files + manifest.json -│ └── agent-model/ ← Robot GLB models -├── vlm-server/ -│ └── asset-library.json ← Persisted asset library data -├── copy-sources.sh ← Sync engine from SimStudio -└── update-sims.sh ← Rebuild scene manifest +## Authoring + +- New scenes: see [docs/scenes.md](docs/scenes.md) +- New evals: see [docs/evals.md](docs/evals.md) +- Architecture overview: see [docs/architecture.md](docs/architecture.md) +- Tour: see [docs/getting-started.md](docs/getting-started.md) + +## Install the CLI (optional) + +If you want `dimsim` as a global command: + +```bash +cd misc/DimSim/cli +deno install -gAf --unstable-net --name=dimsim --config=./deno.json ./cli.ts ``` -## Sync from SimStudio +After install: ```bash -npm run sync +dimsim dev --scene apartment # standalone dev server + browser +dimsim eval list # list workflows under scenes/*/evals/ +dimsim eval go-to-couch # run one workflow against an open sim +dimsim eval --headless --scene apartment # full headless run (CI) ``` -## Add/remove scenes +## Build manually -Drop `.json` files in `public/sims/`, then: ```bash -npm run update-sims +npm install # browser deps (three, rapier, spark, vite) +npm run build # → dist/ ``` diff --git a/misc/DimSim/cli/README.md b/misc/DimSim/cli/README.md deleted file mode 100644 index 613e5d6b78..0000000000 --- a/misc/DimSim/cli/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# DimSim - -3D simulation environment for the [dimos](https://github.com/dimensionalOS/dimos) robotics stack. - -Browser-based Three.js + Rapier simulator with LCM transport, sensor publishing (RGB, depth, LiDAR, odometry), and an eval harness for automated testing of navigation and perception pipelines. - -## Install - -```sh -deno install -gAf --unstable-net jsr:@antim/dimsim -``` - -## Setup - -Download core assets (~22 MB) and install a scene: - -```sh -dimsim setup -dimsim scene install apt -``` - -## Run - -Start the dev server and open the URL it prints: - -```sh -dimsim dev --scene apt -``` - -Run headless evals in CI: - -```sh -dimsim eval --headless --env apt --workflow reach-vase -``` - -## Programmatic API - -```ts -import { startBridgeServer } from "@antim/dimsim"; - -startBridgeServer({ port: 8090, distDir: "./dist", scene: "apt" }); -``` - -## Commands - -| Command | Description | -|---------|-------------| -| `dimsim setup` | Download core assets | -| `dimsim scene install ` | Install a scene | -| `dimsim scene list` | List available and installed scenes | -| `dimsim scene remove ` | Remove a scene | -| `dimsim dev [--scene ]` | Dev server (open browser manually) | -| `dimsim eval --headless` | Run eval workflows in CI | -| `dimsim agent` | Launch dimos Python agent | - -## License - -MIT diff --git a/misc/DimSim/deno.lock b/misc/DimSim/deno.lock deleted file mode 100644 index d1b33f132e..0000000000 --- a/misc/DimSim/deno.lock +++ /dev/null @@ -1,122 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@antim/dimsim@*": "0.1.3", - "jsr:@antim/dimsim@0.1.28": "0.1.28", - "jsr:@antim/dimsim@0.1.29": "0.1.29", - "jsr:@dimos/lcm@*": "0.2.0", - "jsr:@dimos/lcm@0.2.0": "0.2.0", - "jsr:@dimos/msgs@0.1.4": "0.1.4", - "jsr:@dimos/msgs@~0.1.4": "0.1.4", - "jsr:@std/assert@*": "1.0.19", - "jsr:@std/cli@^1.0.28": "1.0.28", - "jsr:@std/encoding@^1.0.10": "1.0.10", - "jsr:@std/fmt@^1.0.9": "1.0.9", - "jsr:@std/fs@^1.0.23": "1.0.23", - "jsr:@std/html@^1.0.5": "1.0.5", - "jsr:@std/http@1": "1.0.25", - "jsr:@std/internal@^1.0.12": "1.0.12", - "jsr:@std/media-types@^1.1.0": "1.1.0", - "jsr:@std/net@^1.0.6": "1.0.6", - "jsr:@std/path@1": "1.1.4", - "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/streams@^1.0.17": "1.0.17" - }, - "jsr": { - "@antim/dimsim@0.1.3": { - "integrity": "b145e83a4545ba03004ccfcd0c14afad3cb9af3258f8174b0f0a8cc64b9949da", - "dependencies": [ - "jsr:@std/http", - "jsr:@std/path@1" - ] - }, - "@antim/dimsim@0.1.28": { - "integrity": "ad194c3c478a403d31b9320df6fe14d52783a26fa6c8c25a6ea57863926513a4", - "dependencies": [ - "jsr:@dimos/msgs@~0.1.4", - "jsr:@std/http", - "jsr:@std/path@1" - ] - }, - "@antim/dimsim@0.1.29": { - "integrity": "b8d6c2edc8bf82c6b83ac01fa8cc24f16332aadbf8b1e46733bcdf9c7d9e77b6", - "dependencies": [ - "jsr:@dimos/msgs@~0.1.4", - "jsr:@std/http", - "jsr:@std/path@1" - ] - }, - "@dimos/lcm@0.2.0": { - "integrity": "03399f5e4800f28a0c294981e0210d784232fc65a57707de19052ad805bd5fea" - }, - "@dimos/msgs@0.1.4": { - "integrity": "564bc30b4bc41a562c296c257a15055283ca0cbd66d0627991ede5295832d0c4" - }, - "@std/assert@1.0.19": { - "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/cli@1.0.28": { - "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a" - }, - "@std/encoding@1.0.10": { - "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" - }, - "@std/fmt@1.0.9": { - "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" - }, - "@std/fs@1.0.23": { - "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37" - }, - "@std/html@1.0.5": { - "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" - }, - "@std/http@1.0.25": { - "integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193", - "dependencies": [ - "jsr:@std/cli", - "jsr:@std/encoding", - "jsr:@std/fmt", - "jsr:@std/fs", - "jsr:@std/html", - "jsr:@std/media-types", - "jsr:@std/net", - "jsr:@std/path@^1.1.4", - "jsr:@std/streams" - ] - }, - "@std/internal@1.0.12": { - "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" - }, - "@std/media-types@1.1.0": { - "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" - }, - "@std/net@1.0.6": { - "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" - }, - "@std/path@1.1.4": { - "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/streams@1.0.17": { - "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" - } - }, - "workspace": { - "packageJson": { - "dependencies": [ - "npm:@dimforge/rapier3d-compat@0.14", - "npm:@sparkjsdev/spark@latest", - "npm:cors@^2.8.5", - "npm:express@^4.21.0", - "npm:openai@^4.77.0", - "npm:three@0.168", - "npm:vite@^5.4.10" - ] - } - } -} diff --git a/misc/DimSim/docs/architecture.md b/misc/DimSim/docs/architecture.md new file mode 100644 index 0000000000..307a4ac399 --- /dev/null +++ b/misc/DimSim/docs/architecture.md @@ -0,0 +1,118 @@ +# DimSim architecture + +``` + dimos (Python) + │ + │ LCM/UDP + control WS + ▼ + ┌───────────────────────────────────────┐ + │ Bridge (Deno, cli/bridge/) │ + │ • LCM multicast (/cmd_vel, /odom, │ + │ /color_image, /lidar/points) │ + │ • Server-side Rapier physics step │ + │ • WS relay to browser │ + └─────────────────┬─────────────────────┘ + │ WebSocket (control + sensors) + ▼ + ┌───────────────────────────────────────┐ + │ Browser engine (vite-bundled, │ + │ src/engine.js + friends) │ + │ • Three.js scene + agent visual │ + │ • Browser-side Rapier (display) │ + │ • Loads scenes//index.js │ + │ • Loads scenes//evals/*.js │ + │ (when triggered) │ + └───────────────────────────────────────┘ +``` + +## File layout + +``` +misc/DimSim/ + src/ ← vite-bundled browser code + engine.js + AiAvatar.js # agent visual + capsule colliders + main.js / style.css # entry + UI + bridge.ts # browser WS client to the bridge + sceneApi.ts # the API scenes import from + sceneEditor.ts # runtime exec sandbox (for SceneClient SDK) + + cli/ ← Deno-only code (not bundled) + cli.ts # `dimsim` CLI entrypoint + deno.json # JSR import map + tasks + bridge/server.ts # the bridge server itself + bridge/lidar.ts # server-side ray-cast lidar + bridge/physics.ts # server-side Rapier step + headless/launcher.ts # Playwright bootstrap + vendor/lcm/* # vendored @dimos/lcm w/ macOS multicast fix + test/dimos_integration.py # LCM smoke test + agent.py # dimos Python agent runner + + evals/ ← eval system, both runtimes + harness.ts # browser orchestrator (bundled → dimsim-eval.js) + rubrics.ts # objectDistance, radiusContains, helpers + runner.ts # Deno runner — walks scenes/, dispatches via WS + deno-client.ts # Deno `@dimsim/eval` (for direct deno-run) + deno.json # @std/path import map + Deno LSP hint + + scenes/ ← user-authored scenes + / + index.js # default-exports an async build({...}) function + data/, textures/ # optional, scene-specific + evals/.js # workflow files (importable in both browser + Deno) + deno.json # @dimsim/eval → ../evals/deno-client.ts + + public/ # static assets, copied by vite + agent-model/dimsim_unitree_stub.glb + logo.svg + + index.html # boot html (carries the importmap) + vite.config.js # pins evals/harness.ts → dist/assets/dimsim-eval.js + package.json # vite + three + rapier + spark (browser only) +``` + +## Key contracts + +**Bridge ↔ browser** — WebSockets on port 8090: + +- `/?ch=control` — text JSON commands (runEval, embodimentConfig, exec, …) + LCM binary frames +- `/?ch=sensors` — sensor publishes (lidar, snapshots) +- `/?ch=rgb`, `/?ch=depth` — image streams + +**Bridge ↔ dimos** — LCM multicast at `239.255.76.67:7667`: + +- `/cmd_vel#geometry_msgs.Twist` — in +- `/odom#geometry_msgs.PoseStamped` — out +- `/color_image#sensor_msgs.Image` — out +- `/lidar/points#sensor_msgs.PointCloud2` — out + +**Scene module** — `scenes//index.js`: + +```js +export default async function build(api) { + const { scene, THREE, physics, setSky, loadGLTF, loadLevel } = api; + // … construct meshes, add colliders, set lighting … + return { embodiment: null, spawnPoint: { x: 0, y: 0.5, z: 0 } }; +} +``` + +**Eval workflow** — `scenes//evals/.js`: + +```js +import { runEval } from '@dimsim/eval'; +await runEval({ + scene: '', + task: 'human-readable description', + timeoutSec: 30, + startPose: { x: 0, y: 0.5, z: 0, yaw: 0 }, + setup: async (ctx) => { /* optional */ }, + success: (ctx) => ctx.rubrics.objectDistance({ target: 'X', thresholdM: 2.0 }), +}); +``` + +The `@dimsim/eval` specifier resolves two ways via two import maps: + +- **Browser** (`index.html`) → `/assets/dimsim-eval.js` (bundled harness chunk, pinned by vite.config.js) +- **Deno** (`scenes/deno.json`) → `evals/deno-client.ts` (dispatches to the bridge via WS) + +Same workflow file, two runtimes. diff --git a/misc/DimSim/docs/evals.md b/misc/DimSim/docs/evals.md new file mode 100644 index 0000000000..7a4de47554 --- /dev/null +++ b/misc/DimSim/docs/evals.md @@ -0,0 +1,109 @@ +# Authoring evals + +An eval workflow is a JS file at `scenes//evals/.js` that imports `runEval` from `@dimsim/eval` and calls it. The file is a runnable program — same shape whether the browser is executing it (when triggered from the CLI / runner) or Deno is (when you `deno run` the file directly to dispatch it at a running bridge). + +## Minimal workflow + +```js +// scenes/apartment/evals/go-to-couch.js +import { runEval } from '@dimsim/eval'; + +await runEval({ + scene: 'apartment', + task: 'Go to the couch', + timeoutSec: 30, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional', thresholdM: 2.0 }), +}); +``` + +That's it. Drop the file under any scene's `evals/` folder and `dimsim eval list` will pick it up. + +## The workflow object + +| Field | Required | Description | +|---|---|---| +| `scene` | ✓ | Scene name. Must match the directory under `scenes/`. | +| `task` | ✓ | Human-readable goal description. Shown in the overlay + logged. | +| `success(ctx)` | ✓ | Function returning `{ passed: bool, reason?, score? }`. Called every 250 ms until it passes or timeout. | +| `timeoutSec` | – | Default 120. Max wall-clock seconds before forced fail. | +| `startPose` | – | `{x, y, z, yaw?}` (yaw in degrees). Applied to the agent before `setup`. | +| `setup(ctx)` | – | Optional async fn that runs once at start. Spawn obstacles, configure embodiments, etc. | + +## The `ctx` object + +`setup(ctx)` and `success(ctx)` both receive: + +| Field | Description | +|---|---| +| `ctx.agent` | The live AiAvatar — has `setPosition`, `getPosition`, `group`, etc. | +| `ctx.agentPos` | `{x, y, z}` — convenience copy of the current agent translation | +| `ctx.sceneState` | `{ assets: AssetEntry[], agentPos }` — for rubric scoring | +| `ctx.setAgentPose({x, y, z, yaw?})` | Teleport the agent (no-op if no agent) | +| `ctx.findAsset(query)` | Case-insensitive search by title or id; returns an AssetEntry or null | +| `ctx.dist(a, b)` | Euclidean distance helper | +| `ctx.rubrics.objectDistance({target, thresholdM?})` | Built-in rubric — bbox-surface distance from agent to target asset | +| `ctx.rubrics.radiusContains({targets[], radiusM?})` | Pass if agent is within `radiusM` of the targets' centroid | + +You can write `success` inline if neither built-in rubric fits: + +```js +success: ({ agent, findAsset, dist }) => { + const tv = findAsset('television'); + const couch = findAsset('sectional'); + if (!tv || !couch) return { passed: false, reason: 'targets missing' }; + const between = { x: (tv.transform.x + couch.transform.x) / 2, y: 0, z: (tv.transform.z + couch.transform.z) / 2 }; + const d = dist(agent.getPosition?.()?.[0] ? { x: agent.getPosition()[0], y: 0, z: agent.getPosition()[2] } : {x:0,y:0,z:0}, between); + return { passed: d <= 1.5, score: d, reason: `${d.toFixed(2)}m from midpoint` }; +} +``` + +## Three ways to run a workflow + +1. **From the CLI against an already-running sim** (most common during dev): + + ```bash + dimsim eval go-to-couch # bare name + dimsim eval apartment/go-to-couch # scene-qualified + ``` + + Auto-implies `--connect`. The sim has to be open already (`dimos --simulation dimsim ...` or `dimsim dev`). + +2. **Directly via `deno run`** — runs the workflow file as a Deno program; dispatches to whichever bridge is on `localhost:8090`: + + ```bash + deno run -A misc/DimSim/scenes/apartment/evals/go-to-couch.js + ``` + + Functionally identical to `dimsim eval go-to-couch`. Useful if you want to drop the `dimsim` global install. + +3. **Headless / CI**: + + ```bash + dimsim eval --headless --scene apartment --workflow go-to-couch + dimsim eval --headless # runs all workflows for all scenes + dimsim eval --headless --output junit > junit.xml + ``` + + Spins up its own bridge and a headless Chromium, no dimos involvement. Designed for CI. + +## How the same file runs in two runtimes + +``` +import { runEval } from '@dimsim/eval'; +``` + +| Runtime | `@dimsim/eval` resolves to | `runEval(workflow)` does | +|---|---|---| +| **Browser** (via `index.html` importmap) | `/assets/dimsim-eval.js` (bundled harness chunk) | Sets up the eval in-place — runs `setup`, polls `success`, returns when passed or timeout | +| **Deno** (via `scenes/deno.json` importmap) | `evals/deno-client.ts` | Computes the workflow URL from `Deno.mainModule`, opens a WS to `localhost:8090`, sends `{type:'runEval', workflowUrl}`, awaits the result, exits | + +The `success` callback never actually runs in Deno — Deno only ships the URL. The browser re-imports the same file URL and executes the callbacks there. + +## Tips + +- **Set `timeoutSec` higher when running on CPU / SwiftShader** — agent setup can take 5–10s on slow renders. +- **Use a `--connect` run first** to confirm the eval logic works against your live sim. Once green, set up CI with `--headless`. +- **`startPose` is yaw-in-degrees**, not radians. `yaw: 90` looks along +X. +- **Score is yours to define.** Lower-is-better for distance-style rubrics, higher-is-better for coverage-style. CI consumers should not assume a meaning. +- **One eval at a time.** The harness is a singleton — running two evals concurrently isn't supported. Use `--parallel N` with multiple browser pages if you need throughput. diff --git a/misc/DimSim/docs/getting-started.md b/misc/DimSim/docs/getting-started.md new file mode 100644 index 0000000000..734e0abd26 --- /dev/null +++ b/misc/DimSim/docs/getting-started.md @@ -0,0 +1,103 @@ +# Getting started + +This is the 5-minute tour. For deeper guides see [scenes.md](scenes.md) and [evals.md](evals.md). + +## The two ways DimSim runs + +``` +dimos drives DimSim (production / agentic / CI) + .venv/bin/dimos --simulation dimsim --dimsim-scene=apartment run unitree-go2-agentic + +DimSim runs standalone (engine dev / scene authoring) + cd misc/DimSim/cli + deno run -A --unstable-net cli.ts dev --scene apartment +``` + +Both end up with a Vite-built `dist/` and a bridge on port 8090. Both serve the same scenes. Difference is just who's driving the agent. + +If you have `dimsim` installed globally: + +```bash +cd misc/DimSim/cli +deno install -gAf --unstable-net --name=dimsim --config=./deno.json ./cli.ts +``` + +…you can replace the `deno run -A --unstable-net cli.ts` boilerplate with just `dimsim`. + +## A 60-second loop: edit a scene and see it + +1. `dimos --simulation dimsim --dimsim-scene=warehouse --no-dimsim-headless run unitree-go2-basic` +2. Wait for the browser to open and the agent to spawn. +3. Open `misc/DimSim/scenes/warehouse/index.js` in your editor. +4. Change `setSky({ brightness: 0.7 })` to `setSky({ brightness: 1.5 })`. Save. +5. The browser HMR-reloads — no full refresh needed. Brighter sky. + +## A 60-second loop: write a new eval + +1. Create `misc/DimSim/scenes/apartment/evals/look-at-window.js`: + + ```js + import { runEval } from '@dimsim/eval'; + + await runEval({ + scene: 'apartment', + task: 'Stand near a window', + timeoutSec: 20, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + success: (ctx) => ctx.rubrics.objectDistance({ target: 'window', thresholdM: 1.5 }), + }); + ``` + +2. From the sim that's already running: + + ```bash + dimsim eval look-at-window + ``` + + You'll see a green/red overlay in the browser when it finishes, plus the result echoed in your terminal. + +## Where things live + +``` +src/ engine + scene API + bridge client (browser, vite-bundled) +cli/ dimsim CLI + bridge server (Deno) +evals/ eval harness + rubrics + Deno client (both runtimes) +scenes/ YOUR scenes — author here +public/ static assets (default robot GLB) +docs/ guides +``` + +See [architecture.md](architecture.md) for the full tree + key contracts. + +## Quick reference + +| What | How | +|---|---| +| Launch sim + open browser | `dimsim dev --scene ` | +| Launch headless | `dimsim dev --scene --headless` | +| List eval workflows | `dimsim eval list` | +| Run one eval against open sim | `dimsim eval ` | +| Run one eval headless | `dimsim eval --headless --scene --workflow ` | +| Run all evals for a scene | `dimsim eval --headless --scene ` | +| Run all evals, JUnit XML out | `dimsim eval --headless --output junit > junit.xml` | +| Direct workflow execution | `deno run -A scenes//evals/.js` | +| Build the frontend manually | `cd misc/DimSim && npm run build` | +| Spin up a profiler | `bash scripts/profile-live.sh` | +| Verify cmd_vel → odom round-trip | `python cli/test/dimos_integration.py` | + +## What about apt.json? + +The old apartment scene used to ship as one 97 MB `apt.json`. It was decomposed into JS-authored modules under `scenes/apartment/data/` plus a `textures/` folder. If you want to regenerate it from an apt.json blob you have lying around: + +```bash +python scripts/extract_apt_to_js.py /path/to/apt.json +``` + +The output goes back into `scenes/apartment/data/` and `scenes/apartment/textures/`. + +## Common gotchas + +- **Headless browser slow to boot** on CI — use `--render cpu` and bump `--timeout 120000`. +- **macOS multicast loopback** — DimSim ships a vendored `@dimos/lcm` with a `setLoopback(true)` patch. The JSR-published version doesn't have it; dropping the vendor will break dimos↔DimSim LCM on macOS until the upstream lands the fix. +- **`unitree-go2-basic` hides lidar in Rerun** — that blueprint sets `world/lidar` visible=false in its override. Click the eye icon in the Rerun entity tree, or use `unitree-go2-spatial` / `unitree-go2-agentic` which leave it visible. +- **Click-to-nav** in Rerun only works on nav-enabled blueprints (`unitree-go2-agentic` and friends). `unitree-go2-basic` has no nav stack. diff --git a/misc/DimSim/docs/scenes.md b/misc/DimSim/docs/scenes.md new file mode 100644 index 0000000000..2f21a06d88 --- /dev/null +++ b/misc/DimSim/docs/scenes.md @@ -0,0 +1,86 @@ +# Authoring scenes + +A scene is a JS module at `scenes//index.js` that default-exports an async `build(api)` function. dimsim dynamically imports it at boot, hands you the scene API, and lets you do whatever you'd do in a normal Three.js script — `scene.add(mesh)`, attach lights, register physics colliders. + +## Minimal scene + +```js +// scenes/warehouse/index.js +export default async function build({ scene, THREE, physics, setSky }) { + setSky({ topColor: '#3a4654', horizonColor: '#cfd6df', brightness: 0.7 }); + + // floor + const floor = new THREE.Mesh( + new THREE.BoxGeometry(30, 0.2, 40), + new THREE.MeshPhysicalMaterial({ color: 0x6b6f74, roughness: 0.95 }), + ); + floor.position.y = -0.1; + scene.add(floor); + physics.staticCollider(floor, 'box'); // make it solid for the agent + lidar + + // sun + ambient + scene.add(new THREE.HemisphereLight(0xffffff, 0x404040, 0.8)); + const sun = new THREE.DirectionalLight(0xffffff, 1.2); + sun.position.set(10, 20, 10); + sun.castShadow = true; + scene.add(sun); + + return { + embodiment: null, // dimos sends the embodiment over WS + spawnPoint: { x: 0, y: 0.5, z: 0 }, + }; +} +``` + +Run it: + +```bash +dimsim dev --scene warehouse +# or via dimos: +dimos --simulation dimsim --dimsim-scene=warehouse run unitree-go2-basic +``` + +A live example with stacked crates, pallet racks, and pendant lights is at `scenes/warehouse/index.js`. + +## The `api` argument + +`build(api)` receives an object — destructure what you need: + +| Field | Description | +|---|---| +| `scene` | `THREE.Scene` — add meshes, lights, groups here | +| `THREE` | The engine's THREE module; use this rather than re-importing | +| `physics.staticCollider(mesh, shape)` | Attach a static collider. `shape`: `"box"` / `"trimesh"` / `"sphere"` | +| `physics.dynamicCollider(mesh, {mass, shape})` | Rigid body — falls, bounces. Engine syncs its position into `mesh` each frame | +| `setSky(opts)` | `{topColor, horizonColor, bottomColor, brightness, softness, sunStrength, sunHeight}` | +| `loadGLTF(url)` | Async GLB loader. Returns `{ scene, animations, ... }` | +| `loadLevel(data)` | Feed an apt-shape blob through the engine's importLevelFromJSON — populates the assets registry so E-key pickup / multi-state objects work | +| `loadJson(url)` | Like loadLevel, but fetches a JSON file first | +| `agent`, `camera`, `renderer`, `RAPIER`, `rapierWorld` | Live references — same instances the engine uses | + +You can also `import * as THREE from 'three'` if you prefer the explicit form, but the `api.THREE` is guaranteed to be the same instance the engine uses (avoiding "two THREEs" bugs). + +## Adding interactive objects (limitation) + +Right now, anything you add with `scene.add(mesh)` is **invisible to the E-key interaction system**. Pickable items, multi-state cabinets, openable doors, the TV-toggle pattern — all of these live in the engine's `assets[]` registry, which is only populated by `loadLevel(data)` / `loadJson(url)` with apt-shape data. + +Two options today: + +1. **Decompose your scene's interactive objects into apt-shape data** (see `scenes/apartment/data/objects.js` for the format) and feed them through `loadLevel`. Tedious but works. +2. **Hold for the `registerAsset(group, {pickable, states, actions})` API** — planned but not yet built. Will let `new THREE.Group(...)` participate in interactions without the apt-shape detour. + +The apartment scene (`scenes/apartment/index.js`) uses option 1 — its `data/` folder has 123 assets including 28 interactive ones. + +## Hot reload + +dimsim's bridge watches `scenes//` and re-fires the engine's HMR handler on any file save. You'll see a `[bridge] hot-reload …` line in the bridge logs, then the engine logs `[dimos] hot-reloaded `. No browser refresh needed. + +Limitation: HMR re-runs `build(api)` which re-adds everything — but the engine doesn't currently scrub interactive state from `loadLevel`. If your scene uses `loadLevel`, expect duplicate assets after HMR; refresh the browser instead. + +## Tips + +- **Always pair `scene.add(mesh)` with `physics.staticCollider(mesh, ...)` if the agent / lidar should see it.** Visual-only meshes are fine — lidar won't hit them. +- **Use `MeshPhysicalMaterial`** for anything you care about visually — supports clearcoat, sheen, transmission, iridescence. +- **For repeating elements** (racks, pillars, crates) use loops in `build`. The whole module re-runs on HMR, so iteration is cheap. +- **Spawn point**: `spawnPoint` from `build`'s return value is where the agent gets placed at scene-load time. dimos may override it later. +- **`scenes/empty/index.js`** is the bare-minimum scene — a floor and a sky. Useful as a starting template. diff --git a/misc/DimSim/docs/sdk-design.md b/misc/DimSim/docs/sdk-design.md deleted file mode 100644 index 62b3851c40..0000000000 --- a/misc/DimSim/docs/sdk-design.md +++ /dev/null @@ -1,253 +0,0 @@ -# DimSim Scene SDK — Design + Tickets - -**Issue**: dimensionalOS/dimos#1691 (Simulation Editing) -**Date**: 2026-03-27 - ---- - -## Coverage Matrix - -Every request from #1691 mapped to a ticket: - -| #1691 Request | Ticket | -|---------------|--------| -| Model importing (GLTFLoader API) | SDK-1 | -| Standard Three.js API (primitives, scenegraph, materials, textures, lights, cameras, shadows, fog) | SDK-1 | -| Optional physics for models (collisions toggle) | SDK-1, ENG-1 | -| Robot definition API (drone, holonomic, plane, car) | SDK-2 | -| Validation: deploy robot in random Three.js environment | VAL-1 | -| Validation: third-party map load (Sketchfab GLB) | VAL-1 | -| Validation: add red ball with collisions | VAL-1 | -| Validation: editing flow (change position, reload, check) | ENG-2 | -| Validation: new embodiment (code a drone) | VAL-2 | - ---- - -## Architecture - -``` -Developer code (TypeScript) DimSim Runtime (browser) -┌─────────────────────────┐ ┌──────────────────────┐ -│ import { Scene, Robot │ │ engine.js │ -│ } from "dimsim/sdk" │ │ importLevelFromJSON()│ -│ │ JSON │ AiAvatar / Robot cfg │ -│ scene.addModel(...) │ ──────► │ Rapier physics │ -│ scene.addBox(...) │ │ Three.js renderer │ -│ scene.setRobot(...) │ │ Eval harness │ -│ scene.export("x.json") │ └──────────────────────┘ -└─────────────────────────┘ - │ - ▼ -┌─────────────────────────┐ -│ dimsim dev --scene x │ -│ dimsim eval --scene x │ -└─────────────────────────┘ -``` - -**Key principle**: The SDK outputs DimSim's existing scene JSON format. -The runtime already consumes it. Most tickets need zero engine changes. - ---- - -## Tickets - -### SDK-1: Scene builder API — models, primitives, lights, materials, physics - -**What**: TypeScript `Scene` class that builds scene JSON. Covers model importing, Three.js primitives, materials, lights, optional physics — the core of the issue. - -**Scope**: -- `Scene` class with `addBox`, `addSphere`, `addCylinder`, `addCone`, `addTorus`, `addPlane` -- `addModel(id, { url, physics, collider, position, rotation, scale })` — GLB/GLTF by URL -- `addPointLight`, `addDirectionalLight`, `addSpotLight`, `addAmbientLight` -- `sky({ topColor, horizonColor, bottomColor, brightness, sunStrength })` -- `fog({ color, near, far })` — Three.js fog -- `addTag(name, { position })` — navigation waypoints for evals -- `group(name)` — scenegraph grouping, children inherit transform -- `setSpawnPoint({ x, y, z, yaw })` -- PBR materials: `{ color, roughness, metalness, specularIntensity, envMapIntensity }` -- Shadow config per object: `castShadow`, `receiveShadow` -- Physics per object: `physics: true/false`, `dynamic: true/false`, `collider: "auto" | "box" | "trimesh" | "convex"` -- `scene.export(filepath)` writes JSON, `scene.toJSON()` returns raw object -- `dimsim dev --scene ./file.json` accepts local file paths (not just S3 manifest names) -- Published as `@antim/dimsim/sdk` export from existing JSR package - -```typescript -import { Scene } from "@antim/dimsim/sdk"; - -const scene = new Scene("fps-arena"); -scene.addModel("map", { url: "./models/lowpoly-fps-map.glb", physics: true }); -scene.addSphere("ball", { - radius: 0.3, position: [2, 2, 0], - material: { color: "#FF0000" }, - physics: true, dynamic: true, -}); -scene.addPointLight("sun", { position: [0, 10, 0], intensity: 3 }); -scene.setSpawnPoint({ x: 0, y: 0.5, z: 3 }); -await scene.export("./scenes/fps-arena.json"); -``` - -**Files**: `sdk/mod.ts`, `sdk/scene.ts`, `sdk/primitives.ts`, `sdk/model.ts`, `sdk/light.ts`, `sdk/types.ts`, `cli.ts` (--scene filepath) - ---- - -### SDK-2: Robot definition in scene JSON - -**What**: `robot` field in scene JSON so developers configure the agent type, collider, avatar, and controller from code. AiAvatar reads this at spawn instead of using hardcoded values. - -**Scope**: -- SDK: `scene.setRobot(Robot.capsule({ ... }))`, `Robot.drone({ ... })`, `Robot.car({ ... })`, `Robot.holonomic({ ... })` -- Scene JSON gets `robot` field: - ```json - { "robot": { "type": "drone", "radius": 0.15, "height": 0.08, "avatar": "./drone.glb", "walkSpeed": 5.0, "hoverHeight": 1.5 } } - ``` -- AiAvatar reads `sceneJson.robot` for collider size, avatar GLB, speed, and controller type -- `capsule` = current behavior (kinematic character controller) -- `drone` = same controller but Y-axis movement enabled, hover height default, cmd_vel.linear.z maps to vertical -- `holonomic` = omnidirectional, cmd_vel.linear.y maps to strafe -- `car` = box collider, linear.x = throttle, angular.z = steering angle -- All types consume standard `cmd_vel` Twist — dimos nav stack works unchanged - -```typescript -scene.setRobot(Robot.drone({ - radius: 0.15, - height: 0.08, - walkSpeed: 5.0, - hoverHeight: 2.0, - avatar: "./models/quadcopter.glb", -})); -``` - -**Files**: `sdk/robot.ts`, `src/AiAvatar.js` (read config from scene), `src/engine.js` (pass robot config) - ---- - -### ENG-1: Dynamic rigid bodies in engine - -**What**: Support `dynamic: true` on primitives and models so objects respond to gravity and collisions (not just static walls/floors). - -**Scope**: -- Currently all colliders use `RigidBodyDesc.fixed()` (immovable) -- Add `RigidBodyDesc.dynamic()` path when scene JSON has `dynamic: true` -- Expose `mass`, `friction`, `restitution` in primitive/model JSON -- Rapier already supports all of this — just need to wire the properties through - -**Covers**: "Add a red ball with collisions enabled" — ball should fall and roll, not float in place. - -**Files**: `src/engine.js` (collider builder sections: `buildPrimitiveCollider`, `buildRapierTriMeshColliderFromObject`) - ---- - -### ENG-2: Hot reload (`--watch`) - -**What**: `dimsim dev --scene ./file.json --watch` watches the JSON file and auto-reloads when it changes. The "reasonable editing flow" from the issue. - -**Scope**: -- `Deno.watchFs()` on scene file path in bridge server -- On change: read new JSON, send `{ type: "reloadScene", scene: }` to browser via WS -- Browser calls `importLevelFromJSON()` (already exists) -- Developer flow: edit `build-scene.ts` → save → re-run → scene JSON updates → browser reloads automatically -- ~20 lines of code in bridge server - -**Covers**: "change the position of an object, reload, check, add a light, reload" - -**Files**: `bridge/server.ts`, `cli.ts` (--watch flag) - ---- - -### VAL-1: Demo scenes — Three.js env, Sketchfab map, custom objects - -**What**: Three example scripts proving the SDK works end-to-end. Each one is `deno run examples/X.ts` → produces JSON → `dimsim dev --scene ./output.json`. - -**Scope**: -- `examples/threejs-env.ts` — load a Three.js example GLB (e.g. LittlestTokyo), add lights, deploy robot -- `examples/sketchfab-map.ts` — load a Sketchfab low-poly FPS map GLB with physics, deploy robot -- `examples/custom-scene.ts` — floor + walls + red ball (dynamic) + lights + fog, robot navigates -- README with run instructions - -**Covers**: All three validation scenarios from #1691 - -**Files**: `examples/*.ts`, `examples/README.md` - ---- - -### VAL-2: Demo — drone embodiment - -**What**: Example script deploying a drone robot in a scene, validating the robot definition API works. - -**Scope**: -- `examples/drone-demo.ts` — scene with obstacles at varying heights, drone robot config -- Demonstrates: drone avatar loads, drone hovers at configured height, cmd_vel.linear.z controls altitude -- Should require "similar amount of code to doing it in plain Three.js" per the issue - -```typescript -const scene = new Scene("drone-test"); -scene.addModel("building", { url: "./models/building.glb", physics: true }); -scene.setRobot(Robot.drone({ hoverHeight: 2.0, avatar: "./models/drone.glb" })); -scene.setSpawnPoint({ x: 0, y: 2, z: 0 }); -await scene.export("./scenes/drone-test.json"); -``` - -**Covers**: "Code a drone, deploy within a world above" - -**Files**: `examples/drone-demo.ts` - ---- - -## Dependency Graph - -``` -SDK-1 (scene builder) ─────┬──► VAL-1 (3 demo scenes) - │ -SDK-2 (robot config) ──────┼──► VAL-2 (drone demo) - │ -ENG-1 (dynamic bodies) ─────┘ -ENG-2 (hot reload) ── independent -``` - -**Build order**: SDK-1 → (ENG-1 + ENG-2 + SDK-2 in parallel) → (VAL-1 + VAL-2) - ---- - -## File Structure - -``` -DimSim/ -├── sdk/ -│ ├── mod.ts # Public API: export { Scene, Robot } -│ ├── scene.ts # Scene builder class -│ ├── primitives.ts # Primitive type definitions + helpers -│ ├── model.ts # GLB/GLTF model wrapper -│ ├── light.ts # Light types -│ ├── robot.ts # Robot.capsule(), Robot.drone(), etc. -│ └── types.ts # Vec3, Transform, Material, etc. -├── examples/ -│ ├── threejs-env.ts -│ ├── sketchfab-map.ts -│ ├── custom-scene.ts -│ ├── drone-demo.ts -│ └── README.md -├── dimos-cli/ -│ ├── cli.ts # --scene filepath, --watch flag -│ ├── bridge/server.ts # hot reload watcher -│ └── ... -├── src/ -│ ├── engine.js # dynamic bodies, robot config passthrough -│ ├── AiAvatar.js # read robot config from scene JSON -│ └── ... -└── docs/ - └── sdk-design.md # this file -``` - -## JSR exports - -```json -{ - "exports": { - ".": "./cli.ts", - "./mod": "./mod.ts", - "./sdk": "./sdk/mod.ts" - } -} -``` - -Usage: `import { Scene, Robot } from "@antim/dimsim/sdk";` diff --git a/misc/DimSim/package-lock.json b/misc/DimSim/package-lock.json index 3478389ba2..9bd3f917d7 100644 --- a/misc/DimSim/package-lock.json +++ b/misc/DimSim/package-lock.json @@ -10,9 +10,6 @@ "dependencies": { "@dimforge/rapier3d-compat": "^0.14.0", "@sparkjsdev/spark": "latest", - "cors": "^2.8.5", - "express": "^4.21.0", - "openai": "^4.77.0", "three": "^0.168.0" }, "devDependencies": { @@ -417,9 +414,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", "cpu": [ "arm" ], @@ -431,9 +428,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", "cpu": [ "arm64" ], @@ -445,9 +442,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", "cpu": [ "arm64" ], @@ -459,9 +456,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", "cpu": [ "x64" ], @@ -473,9 +470,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", "cpu": [ "arm64" ], @@ -487,9 +484,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", "cpu": [ "x64" ], @@ -501,9 +498,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", "cpu": [ "arm" ], @@ -515,9 +512,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", "cpu": [ "arm" ], @@ -529,9 +526,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", "cpu": [ "arm64" ], @@ -543,9 +540,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", "cpu": [ "arm64" ], @@ -557,9 +554,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", "cpu": [ "loong64" ], @@ -571,9 +568,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", "cpu": [ "loong64" ], @@ -585,9 +582,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", "cpu": [ "ppc64" ], @@ -599,9 +596,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", "cpu": [ "ppc64" ], @@ -613,9 +610,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", "cpu": [ "riscv64" ], @@ -627,9 +624,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", "cpu": [ "riscv64" ], @@ -641,9 +638,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", "cpu": [ "s390x" ], @@ -655,9 +652,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", "cpu": [ "x64" ], @@ -669,9 +666,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", "cpu": [ "x64" ], @@ -683,9 +680,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", "cpu": [ "x64" ], @@ -697,9 +694,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", "cpu": [ "arm64" ], @@ -711,9 +708,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", "cpu": [ "arm64" ], @@ -725,9 +722,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", "cpu": [ "ia32" ], @@ -739,9 +736,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", "cpu": [ "x64" ], @@ -753,9 +750,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", "cpu": [ "x64" ], @@ -767,12 +764,15 @@ ] }, "node_modules/@sparkjsdev/spark": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/@sparkjsdev/spark/-/spark-0.1.10.tgz", - "integrity": "sha512-CiijdZQuj7KPDUqIZPiEqyUkJCYo1JqR05vq/V+ElxMwqR7L70ZuZDyIKcasjZHSiPB8pGRMH8HZGqUKO9aRPQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sparkjsdev/spark/-/spark-2.1.0.tgz", + "integrity": "sha512-BRw+MuMzx0B3K8fDLQygt2OHEhYUV+41RX7btq9pZ3rCVrq42o57jW34VAIvC7JO/84DJh/1AutACV9ym6BfVg==", "license": "MIT", "dependencies": { "fflate": "^0.8.2" + }, + "peerDependencies": { + "three": ">=0.180.0" } }, "node_modules/@types/estree": { @@ -782,312 +782,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1127,153 +821,12 @@ "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1289,238 +842,10 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -1536,133 +861,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/openai": { - "version": "4.104.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", - "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1671,9 +869,9 @@ "license": "ISC" }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -1691,7 +889,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1699,62 +897,10 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", "dev": true, "license": "MIT", "dependencies": { @@ -1768,183 +914,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1955,82 +952,12 @@ "node": ">=0.10.0" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/three": { "version": "0.168.0", "resolved": "https://registry.npmjs.org/three/-/three-0.168.0.tgz", "integrity": "sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==", "license": "MIT" }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -2090,31 +1017,6 @@ "optional": true } } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/misc/DimSim/package.json b/misc/DimSim/package.json index abdc9269cf..8d8971109f 100644 --- a/misc/DimSim/package.json +++ b/misc/DimSim/package.json @@ -3,26 +3,16 @@ "private": true, "version": "1.0.0", "type": "module", - "description": "Standalone 3D simulation runner for SimStudio scenes", + "description": "Browser-based 3D simulator (Three.js + Rapier) + Deno bridge — runs as the simulation backend for dimos.", "scripts": { "dev": "vite --force", "build": "vite build", - "preview": "vite preview", - "server": "node server.js", - "sync": "bash copy-sources.sh", - "parity:check": "bash check-parity.sh", - "update-sims": "bash update-sims.sh", - "dimos": "deno run --allow-all dimos-cli/cli.ts eval", - "dimos:headless": "deno run --allow-all dimos-cli/cli.ts eval --headless", - "dimos:dev": "deno run --allow-all dimos-cli/cli.ts dev" + "preview": "vite preview" }, "dependencies": { "@sparkjsdev/spark": "latest", "@dimforge/rapier3d-compat": "^0.14.0", - "three": "^0.168.0", - "express": "^4.21.0", - "cors": "^2.8.5", - "openai": "^4.77.0" + "three": "^0.168.0" }, "devDependencies": { "vite": "^5.4.10" diff --git a/misc/DimSim/scripts/apt_to_single_glb.py b/misc/DimSim/scripts/apt_to_single_glb.py deleted file mode 100644 index a6d194d03e..0000000000 --- a/misc/DimSim/scripts/apt_to_single_glb.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -""" -Merge apt.json (the original 97MB blob) into a single GLB so you can open it in -Blender / any GLB viewer for A/B comparison against the decomposed version. - -Reuses make_geom / apply_transform / apply_material from decompose_objects_to_glb. - -Usage: python3 scripts/apt_to_single_glb.py -""" - -import argparse -import json -import sys -from pathlib import Path - -import numpy as np -import trimesh - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -from decompose_objects_to_glb import ( - apply_material, - apply_transform, - make_geom, -) - - -def main(apt_path: str, out_path: str) -> None: - with open(apt_path) as f: - doc = json.load(f) - - scene = trimesh.Scene() - skipped = 0 - - # Top-level primitives (structure: walls/floor) - for prim in doc.get("primitives", []): - g = make_geom(prim) - if g is None: - skipped += 1 - continue - apply_material(g, prim.get("material", {})) - apply_transform(g, prim.get("transform", {})) - scene.add_geometry(g) - - # Assets (furniture). Each non-_deltaOnly asset's state has nested primitives. - # Match engine's instantiateAsset: build the asset's geometry, recenter around its - # bbox center, then apply the outer asset transform. - for asset in doc.get("assets", []): - if asset.get("_deltaOnly"): - continue - states = asset.get("states", []) - if not states: - continue - current_id = asset.get("currentStateId", "state-default") - state = next((s for s in states if s.get("id") == current_id), states[0]) - prims = state.get("scene", {}).get("primitives", []) - outer_tr = asset.get("transform", {}) - - built = [] - for prim in prims: - g = make_geom(prim) - if g is None: - skipped += 1 - continue - apply_material(g, prim.get("material", {})) - apply_transform(g, prim.get("transform", {})) - built.append(g) - - if not built: - continue - - all_verts = np.concatenate([m.vertices for m in built]) - bbox_center = (all_verts.min(axis=0) + all_verts.max(axis=0)) / 2.0 - recenter = trimesh.transformations.translation_matrix(-bbox_center) - for m in built: - m.apply_transform(recenter) - apply_transform(m, outer_tr) - scene.add_geometry(m) - - n = len(scene.geometry) - print(f"Composed {n} meshes ({skipped} skipped)") - scene.export(out_path) - size = Path(out_path).stat().st_size - print(f"Wrote {out_path} ({size / 1024:.1f} KB)") - - -if __name__ == "__main__": - ap = argparse.ArgumentParser() - ap.add_argument("apt_path") - ap.add_argument("out_path") - args = ap.parse_args() - main(args.apt_path, args.out_path) diff --git a/misc/DimSim/scripts/decompose_apt.py b/misc/DimSim/scripts/decompose_apt.py deleted file mode 100644 index c50ed6a321..0000000000 --- a/misc/DimSim/scripts/decompose_apt.py +++ /dev/null @@ -1,454 +0,0 @@ -#!/usr/bin/env python3 -""" -One-shot decomposer: scenes/apartment/apt.json → JS skeleton + texture files. - -Run from DimSim repo root: python3 scripts/decompose_apt.py - -Produces: - scenes/apartment/index.js — entry: applies sky, calls shell/furniture/lights - scenes/apartment/shell.js — building shell (80 top-level primitives + colliders) - scenes/apartment/furniture.js — 87 asset instances (each with nested primitives) - scenes/apartment/lights.js — 11 point lights - scenes/apartment/textures/ — 16 unique extracted images (avif/jpg/png) - -apt.json afterwards is dead and should be deleted. -""" - -import base64 -import hashlib -import json -import re -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -OUT = ROOT / "scenes" / "apartment" -TEX_DIR = OUT / "textures" - - -def sniff_format(raw: bytes) -> str: - if raw[:4] == b"\x89PNG": - return "png" - if raw[:2] == b"\xff\xd8": - return "jpg" - if b"ftypavif" in raw[:32] or b"ftypmif1" in raw[:32]: - return "avif" - if b"ftypheic" in raw[:32] or b"ftypheix" in raw[:32]: - return "heic" - if raw[:6] in (b"GIF87a", b"GIF89a"): - return "gif" - if raw[:4] == b"RIFF" and raw[8:12] == b"WEBP": - return "webp" - return "bin" - - -def extract_textures(doc: dict) -> dict[str, str]: - """Walk all materials, write unique textures to disk, return data-url → filename map.""" - TEX_DIR.mkdir(parents=True, exist_ok=True) - seen: dict[str, str] = {} # raw bytes hash → filename - - def collect(node): - if isinstance(node, dict): - mat = node.get("material") - if isinstance(mat, dict): - url = mat.get("textureDataUrl") - if url: - m = re.match(r"^data:([^;]+);base64,(.+)$", url) - if m: - raw = base64.b64decode(m.group(2)) - h = hashlib.sha256(raw).hexdigest()[:12] - if h not in seen: - ext = sniff_format(raw) - name = f"{h}.{ext}" - (TEX_DIR / name).write_bytes(raw) - seen[h] = name - # replace inline url with filename ref for emit step - mat["_texFile"] = seen[h] - mat.pop("textureDataUrl", None) - for v in node.values(): - collect(v) - elif isinstance(node, list): - for v in node: - collect(v) - - collect(doc) - print(f" Extracted {len(seen)} unique textures to {TEX_DIR.relative_to(ROOT)}/") - return seen - - -# ── JS emit ────────────────────────────────────────────────────────────────── - - -def js_hex(color: str | None) -> str: - if not color or not color.startswith("#"): - return "0xffffff" - return f"0x{color[1:].lower()}" - - -def js_num(x: float, ndigits: int = 4) -> str: - """Compact number repr — strip trailing zeros.""" - if x == int(x): - return str(int(x)) - s = f"{x:.{ndigits}f}".rstrip("0").rstrip(".") - return s if s else "0" - - -def emit_material(mat: dict) -> str: - """Emit a JS object literal for material opts passed to the primitive helper. - Mirrors the property mapping in engine.js createPrimitiveMaterial.""" - parts = [] - parts.append(f"color:{js_hex(mat.get('color'))}") - # engine: roughness ← softness (fallback to roughness, default 0.7) - rough = mat.get("softness", mat.get("roughness", 0.7)) - if rough != 0.7: - parts.append(f"r:{js_num(rough, 3)}") - if (m := mat.get("metalness", 0)) != 0: - parts.append(f"m:{js_num(m, 3)}") - if (s := mat.get("specularIntensity", 1)) != 1: - parts.append(f"si:{js_num(s, 3)}") - if (sc := mat.get("specularColor")) and sc != "#ffffff": - parts.append(f"sc:{js_hex(sc)}") - if (ei_v := mat.get("envMapIntensity", 1)) != 1: - parts.append(f"emi:{js_num(ei_v, 3)}") - # engine: clearcoat ← max(clearcoat, hardness*0.85) - hard = mat.get("hardness", 0) - cc = max(mat.get("clearcoat", 0), hard * 0.85) - if cc > 0: - parts.append(f"cc:{js_num(cc, 3)}") - ccr = min(mat.get("clearcoatRoughness", 0), 1 - hard * 0.8) - if ccr > 0: - parts.append(f"ccr:{js_num(ccr, 3)}") - # engine: sheen ← fluffiness - fluff = mat.get("fluffiness", 0) - if fluff > 0: - parts.append(f"sh:{js_num(fluff, 3)}") - if (tr := mat.get("transmission", 0)) > 0: - parts.append(f"tr:{js_num(tr, 3)}") - if (ior := mat.get("ior", 1.45)) != 1.45: - parts.append(f"ior:{js_num(ior, 3)}") - if (thk := mat.get("thickness", 0)) > 0: - parts.append(f"thk:{js_num(thk, 3)}") - if (iri := mat.get("iridescence", 0)) > 0: - parts.append(f"iri:{js_num(iri, 3)}") - if (em := mat.get("emissive")) and em != "#000000": - parts.append(f"e:{js_hex(em)}") - if (ei := mat.get("emissiveIntensity", 1)) != 1: - parts.append(f"ei:{js_num(ei, 3)}") - if (op := mat.get("opacity", 1)) != 1: - parts.append(f"op:{js_num(op, 3)}") - if mat.get("doubleSided"): - parts.append("ds:1") - if (tex := mat.get("_texFile")): - parts.append(f"t:'{tex}'") - uv = mat.get("uvTransform") or {} - uv_parts = [] - for k_in, k_out in [("repeatX", "rx"), ("repeatY", "ry"), - ("offsetX", "ox"), ("offsetY", "oy"), - ("rotationDeg", "rot")]: - v = uv.get(k_in) - if v not in (None, 0, 1): - # repeat defaults to 1; offset/rotation default to 0 — only emit non-default - if (k_in.startswith("repeat") and v != 1) or (not k_in.startswith("repeat") and v != 0): - uv_parts.append(f"{k_out}:{js_num(v, 3)}") - if uv_parts: - parts.append("uv:{" + ",".join(uv_parts) + "}") - return "{" + ", ".join(parts) + "}" - - -def emit_transform(tr: dict) -> str: - """Position/rotation/scale as compact array tuples.""" - pos = tr.get("position", {}) - rot = tr.get("rotation", {}) - scale = tr.get("scale", {}) - p = [js_num(pos.get(k, 0)) for k in "xyz"] - parts = [f"p:[{','.join(p)}]"] - if any(rot.get(k, 0) != 0 for k in "xyz"): - r = [js_num(rot.get(k, 0)) for k in "xyz"] - parts.append(f"r:[{','.join(r)}]") - if any(scale.get(k, 1) != 1 for k in "xyz"): - s = [js_num(scale.get(k, 1)) for k in "xyz"] - parts.append(f"s:[{','.join(s)}]") - return "{" + ", ".join(parts) + "}" - - -def emit_primitive(prim: dict) -> str: - """Emit one primitive() call as a JS data tuple.""" - t = prim.get("type", "box") - dims = prim.get("dimensions", {}) - if t == "box": - d = [js_num(dims.get(k, 1)) for k in ("width", "height", "depth")] - dim_str = f"[{','.join(d)}]" - elif t == "sphere": - dim_str = f"[{js_num(dims.get('radius', 0.5))}]" - elif t == "cylinder": - dim_str = f"[{js_num(dims.get('radiusTop', dims.get('radius', 0.5)))},{js_num(dims.get('radiusBottom', dims.get('radius', 0.5)))},{js_num(dims.get('height', 1))}]" - elif t == "plane": - d = [js_num(dims.get(k, 1)) for k in ("width", "height")] - dim_str = f"[{','.join(d)}]" - elif t == "cone": - dim_str = f"[{js_num(dims.get('radius', 0.5))},{js_num(dims.get('height', 1))}]" - elif t == "torus": - dim_str = f"[{js_num(dims.get('radius', 0.5))},{js_num(dims.get('tube', 0.2))}]" - else: - dim_str = "[]" - tr = emit_transform(prim.get("transform", {})) - mat = emit_material(prim.get("material", {})) - return f"['{t}',{dim_str},{tr},{mat}]" - - -def emit_structure(primitives: list, out_path: Path) -> None: - lines = [ - "// Static geometry — walls, floor, ceiling, fixtures.", - "", - "import { addPrimitives } from './_prim.js';", - "", - "const STRUCTURE = [", - ] - for p in primitives: - lines.append(f" {emit_primitive(p)},") - lines += [ - "];", - "", - "export function buildStructure(scene, THREE, physics) {", - " addPrimitives(scene, THREE, physics, STRUCTURE, { staticColliders: true });", - "}", - "", - ] - out_path.write_text("\n".join(lines)) - - -def emit_objects(assets: list, out_path: Path) -> None: - """Each asset = a Group at the asset's transform, containing its state's primitives.""" - items = [] - for a in assets: - if a.get("_deltaOnly"): - continue - states = a.get("states", []) - if not states: - continue - current_id = a.get("currentStateId", "state-default") - state = next((s for s in states if s.get("id") == current_id), states[0]) - scene_data = state.get("scene", {}) - prims = scene_data.get("primitives", []) - if not prims: - continue - items.append((a.get("title", "object"), a.get("transform", {}), prims)) - - lines = [ - "// Code-built placed items. Each entry is a Group of primitives + a transform.", - "// Future direction: migrate to GLB files loaded via loadGLTF().", - "", - "import { addObjectGroup } from './_prim.js';", - "", - "const OBJECTS = [", - ] - for title, tr, prims in items: - title_js = json.dumps(title) - lines.append(f" {{ title: {title_js}, t: {emit_transform(tr)}, prims: [") - for p in prims: - lines.append(f" {emit_primitive(p)},") - lines.append(" ] },") - lines += [ - "];", - "", - "export function buildObjects(scene, THREE, physics) {", - " for (const item of OBJECTS) {", - " addObjectGroup(scene, THREE, physics, item);", - " }", - "}", - "", - ] - out_path.write_text("\n".join(lines)) - - -def emit_lights(lights: list, out_path: Path) -> None: - lines = [ - "// Auto-generated from apt.json — lights.", - "", - "export function buildLights(scene, THREE) {", - ] - for i, light in enumerate(lights): - t = light.get("type", "point") - color = js_hex(light.get("color")) - intensity = js_num(light.get("intensity", 1)) - pos = light.get("position", {}) - if t == "point": - dist = js_num(light.get("distance", 0)) - lines.append( - f" {{ const l = new THREE.PointLight({color}, {intensity}, {dist});" - f" l.position.set({js_num(pos.get('x', 0))}, {js_num(pos.get('y', 0))}, {js_num(pos.get('z', 0))});" - f" l.castShadow = {str(bool(light.get('castShadow'))).lower()}; scene.add(l); }}" - ) - elif t == "directional": - lines.append( - f" {{ const l = new THREE.DirectionalLight({color}, {intensity});" - f" l.position.set({js_num(pos.get('x', 0))}, {js_num(pos.get('y', 0))}, {js_num(pos.get('z', 0))});" - f" l.castShadow = {str(bool(light.get('castShadow'))).lower()}; scene.add(l); }}" - ) - elif t == "ambient": - lines.append(f" scene.add(new THREE.AmbientLight({color}, {intensity}));") - elif t == "hemisphere": - ground = js_hex(light.get("groundColor")) - lines.append( - f" {{ const l = new THREE.HemisphereLight({color}, {ground}, {intensity});" - f" l.position.set({js_num(pos.get('x', 0))}, {js_num(pos.get('y', 0))}, {js_num(pos.get('z', 0))}); scene.add(l); }}" - ) - lines += ["}", ""] - out_path.write_text("\n".join(lines)) - - -def emit_prim_helper(out_path: Path) -> None: - """The shared primitive-construction helper used by shell.js + furniture.js.""" - content = """// Shared primitive constructor + texture cache. Used by shell.js + furniture.js. -// -// Data tuple shape: ['box'|'sphere'|'cylinder'|'plane'|'cone'|'torus', dims[], transform, material] -// dims: box [w,h,d], sphere [r], cylinder [rTop,rBot,h], plane [w,h], cone [r,h], torus [r,tube] -// transform: { p:[x,y,z], r?:[rx,ry,rz], s?:[sx,sy,sz] } -// material: { color, r?, m?, e?, ei?, t?, op? } // t = texture filename (./textures/) - -const _texCache = new Map(); -const _texBase = new URL('./textures/', import.meta.url).toString(); - -function loadTex(name, THREE) { - if (!_texCache.has(name)) { - const tex = new THREE.TextureLoader().load(_texBase + name); - tex.colorSpace = THREE.SRGBColorSpace; - tex.wrapS = tex.wrapT = THREE.RepeatWrapping; - _texCache.set(name, tex); - } - return _texCache.get(name); -} - -function makeMaterial(THREE, mat) { - const opts = { color: mat.color ?? 0xffffff }; - if (mat.r != null) opts.roughness = mat.r; - if (mat.m != null) opts.metalness = mat.m; - if (mat.e != null) opts.emissive = mat.e; - if (mat.ei != null) opts.emissiveIntensity = mat.ei; - if (mat.op != null) { opts.opacity = mat.op; opts.transparent = mat.op < 1; } - if (mat.t) opts.map = loadTex(mat.t, THREE); - return new THREE.MeshStandardMaterial(opts); -} - -function makeGeometry(THREE, type, dims) { - switch (type) { - case 'box': return new THREE.BoxGeometry(dims[0], dims[1], dims[2]); - case 'sphere': return new THREE.SphereGeometry(dims[0], 24, 24); - case 'cylinder': return new THREE.CylinderGeometry(dims[0], dims[1], dims[2], 24); - case 'plane': return new THREE.PlaneGeometry(dims[0], dims[1]); - case 'cone': return new THREE.ConeGeometry(dims[0], dims[1], 24); - case 'torus': return new THREE.TorusGeometry(dims[0], dims[1], 12, 24); - default: return new THREE.BoxGeometry(1, 1, 1); - } -} - -function applyTransform(obj, tr) { - if (tr.p) obj.position.set(tr.p[0], tr.p[1], tr.p[2]); - if (tr.r) obj.rotation.set(tr.r[0], tr.r[1], tr.r[2]); - if (tr.s) obj.scale.set(tr.s[0], tr.s[1], tr.s[2]); -} - -export function makePrimitive(THREE, tuple) { - const [type, dims, tr, mat] = tuple; - const mesh = new THREE.Mesh(makeGeometry(THREE, type, dims), makeMaterial(THREE, mat)); - applyTransform(mesh, tr); - mesh.castShadow = mesh.receiveShadow = true; - return mesh; -} - -export function addPrimitives(scene, THREE, physics, tuples, opts = {}) { - for (const t of tuples) { - const m = makePrimitive(THREE, t); - scene.add(m); - if (opts.staticColliders) { - const shape = t[0] === 'sphere' ? 'sphere' : (t[0] === 'plane' ? 'box' : t[0] === 'box' ? 'box' : 'trimesh'); - physics.staticCollider(m, shape); - } - } -} - -export function addObjectGroup(scene, THREE, physics, item) { - const g = new THREE.Group(); - applyTransform(g, item.t); - for (const t of item.prims) g.add(makePrimitive(THREE, t)); - scene.add(g); - physics.staticCollider(g, 'trimesh'); -} -""" - out_path.write_text(content) - - -def emit_index(scene_settings: dict, out_path: Path, spawn=(2, 0.5, 3)) -> None: - sky = scene_settings.get("sky", {}) - sky_obj = { - k: v - for k, v in sky.items() - if k in ("topColor", "horizonColor", "bottomColor", "brightness", "softness", "sunStrength", "sunHeight") - } - sky_js = json.dumps(sky_obj, indent=2).replace("\n", "\n ") - content = f"""// scenes/apartment/index.js — entry point for the apartment scene. - -const SKY = {sky_js}; - -export default async function build({{ scene, THREE, physics, setSky }}) {{ - const ts = `?t=${{Date.now()}}`; - const [structure, objects, lights] = await Promise.all([ - import(/* @vite-ignore */ './structure.js' + ts), - import(/* @vite-ignore */ './objects.js' + ts), - import(/* @vite-ignore */ './lights.js' + ts), - ]); - - if (setSky) setSky(SKY); - structure.buildStructure(scene, THREE, physics); - objects.buildObjects(scene, THREE, physics); - lights.buildLights(scene, THREE); - - return {{ - embodiment: null, - spawnPoint: {{ x: {js_num(spawn[0])}, y: {js_num(spawn[1])}, z: {js_num(spawn[2])} }}, - }}; -}} -""" - out_path.write_text(content) - - -# ── Main ───────────────────────────────────────────────────────────────────── - - -def main(apt_path: str = None) -> None: - src = Path(apt_path) if apt_path else (OUT / "apt.json") - if not src.exists(): - print(f"FATAL: {src} not found") - return - - print(f"Reading {src} ({src.stat().st_size / 1e6:.1f} MB)...") - with src.open() as f: - doc = json.load(f) - - print("Extracting textures...") - extract_textures(doc) - - print(f"Emitting structure.js ({len(doc['primitives'])} primitives)...") - emit_structure(doc["primitives"], OUT / "structure.js") - - print(f"Emitting objects.js ({len(doc['assets'])} assets, filtering _deltaOnly)...") - emit_objects(doc["assets"], OUT / "objects.js") - - print(f"Emitting lights.js ({len(doc['lights'])} lights)...") - emit_lights(doc["lights"], OUT / "lights.js") - - emit_prim_helper(OUT / "_prim.js") - emit_index(doc.get("sceneSettings", {}), OUT / "index.js") - - print("\nDone. Files in scenes/apartment/:") - for p in sorted(OUT.iterdir()): - if p.is_file(): - print(f" {p.name:30s} {p.stat().st_size / 1024:8.1f} KB") - elif p.is_dir(): - tot = sum(f.stat().st_size for f in p.iterdir()) - n = sum(1 for _ in p.iterdir()) - print(f" {p.name}/ ({n} files, {tot / 1024:.1f} KB)") - - -if __name__ == "__main__": - import sys - main(sys.argv[1] if len(sys.argv) > 1 else None) diff --git a/misc/DimSim/scripts/decompose_objects_to_glb.py b/misc/DimSim/scripts/decompose_objects_to_glb.py deleted file mode 100644 index d81fef6b86..0000000000 --- a/misc/DimSim/scripts/decompose_objects_to_glb.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python3 -""" -Convert apt.json's 87 furniture assets into individual GLB files + a placements list. - -Each apt asset = a Group of primitive sub-meshes + an outer transform. This script: - * Builds the asset's local geometry (sub-primitives merged) as a trimesh Scene - * Exports to scenes/apartment/objects/.glb - * Dedupes by (sorted) primitive-hash so repeats share one GLB - * Emits scenes/apartment/objects.js — a placements list calling loadGLTF - -Run: python3 scripts/decompose_objects_to_glb.py /tmp/apt.json -""" - -import argparse -import hashlib -import json -import re -from pathlib import Path - -import numpy as np -import trimesh -from trimesh.visual.material import PBRMaterial - - -ROOT = Path(__file__).resolve().parents[1] -OUT = ROOT / "scenes" / "apartment" -GLB_DIR = OUT / "objects" - - -# ── Primitive constructors ─────────────────────────────────────────────────── - - -def _srgb_to_linear(c: float) -> float: - """Convert one sRGB channel (0..1) to linear-light (0..1). glTF baseColorFactor - is in linear space; apt.json colors are sRGB hex.""" - return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 - - -def hex_to_linear_rgba(color: str | None, opacity: float = 1.0) -> tuple[float, float, float, float]: - if not color or not isinstance(color, str) or not color.startswith("#"): - return (_srgb_to_linear(0.78), _srgb_to_linear(0.78), _srgb_to_linear(0.78), opacity) - h = color[1:] - if len(h) == 3: - h = "".join(c * 2 for c in h) - r = _srgb_to_linear(int(h[0:2], 16) / 255) - g = _srgb_to_linear(int(h[2:4], 16) / 255) - b = _srgb_to_linear(int(h[4:6], 16) / 255) - return (r, g, b, opacity) - - -# trimesh creates cylinders/cones along the +Z axis; threejs's CylinderGeometry -# and ConeGeometry are along +Y. Rotate -π/2 around X to map +Z → +Y so the -# baked geometry matches threejs convention. -_Z_TO_Y = trimesh.transformations.rotation_matrix(-np.pi / 2, [1, 0, 0]) - - -def make_geom(prim: dict) -> trimesh.Trimesh | None: - t = prim.get("type", "box") - dims = prim.get("dimensions", {}) - try: - if t == "box": - w = dims.get("width", 1) - h = dims.get("height", 1) - d = dims.get("depth", 1) - return trimesh.creation.box(extents=[w, h, d]) - if t == "sphere": - r = dims.get("radius", 0.5) - return trimesh.creation.uv_sphere(radius=r, count=[16, 16]) - if t == "cylinder": - rt = dims.get("radiusTop", dims.get("radius", 0.5)) - rb = dims.get("radiusBottom", dims.get("radius", 0.5)) - h = dims.get("height", 1) - # trimesh has no native frustum — use average radius for tapered cylinders. - mesh = trimesh.creation.cylinder(radius=(rt + rb) / 2, height=h, sections=24) - mesh.apply_transform(_Z_TO_Y) - return mesh - if t == "cone": - r = dims.get("radius", 0.5) - h = dims.get("height", 1) - mesh = trimesh.creation.cone(radius=r, height=h, sections=24) - # trimesh cone: base at z=0, apex at z=h. threejs ConeGeometry is - # centered (base at y=-h/2, apex at y=h/2). Recenter along z first, - # then rotate +Z → +Y. - mesh.apply_translation([0, 0, -h / 2]) - mesh.apply_transform(_Z_TO_Y) - return mesh - if t == "torus": - r = dims.get("radius", 0.5) - tube = dims.get("tube", 0.2) - # Both trimesh and threejs put torus in the XY plane with axis +Z — no rotation needed. - return trimesh.creation.torus(major_radius=r, minor_radius=tube, major_sections=24, minor_sections=12) - if t == "plane": - w = dims.get("width", 1) - h = dims.get("height", 1) - # threejs PlaneGeometry lies in the XY plane (axis +Z). - verts = np.array([ - [-w / 2, -h / 2, 0], - [w / 2, -h / 2, 0], - [w / 2, h / 2, 0], - [-w / 2, h / 2, 0], - ]) - faces = np.array([[0, 1, 2], [0, 2, 3]]) - return trimesh.Trimesh(vertices=verts, faces=faces, process=False) - except Exception as e: - print(f" WARN: failed to build {t}: {e}") - return None - return None - - -def apply_transform(mesh: trimesh.Trimesh, tr: dict) -> trimesh.Trimesh: - pos = tr.get("position", {}) - rot = tr.get("rotation", {}) - scale = tr.get("scale", {}) - - sx = scale.get("x", 1) - sy = scale.get("y", 1) - sz = scale.get("z", 1) - if sx != 1 or sy != 1 or sz != 1: - S = np.diag([sx, sy, sz, 1.0]) - mesh.apply_transform(S) - - rx = rot.get("x", 0) - ry = rot.get("y", 0) - rz = rot.get("z", 0) - if rx or ry or rz: - # threejs default Euler order is 'XYZ' → matrix M = Rx * Ry * Rz - Rx = trimesh.transformations.rotation_matrix(rx, [1, 0, 0]) - Ry = trimesh.transformations.rotation_matrix(ry, [0, 1, 0]) - Rz = trimesh.transformations.rotation_matrix(rz, [0, 0, 1]) - mesh.apply_transform(Rx @ Ry @ Rz) - - px = pos.get("x", 0) - py = pos.get("y", 0) - pz = pos.get("z", 0) - if px or py or pz: - T = trimesh.transformations.translation_matrix([px, py, pz]) - mesh.apply_transform(T) - - return mesh - - -def apply_material(mesh: trimesh.Trimesh, mat: dict) -> None: - opacity = float(mat.get("opacity", 1)) - color = hex_to_linear_rgba(mat.get("color"), opacity) - rough = float(mat.get("roughness", 0.5)) - metal = float(mat.get("metalness", 0)) - em = mat.get("emissive") - emissive_factor = [0, 0, 0] - if em and em != "#000000": - ei = float(mat.get("emissiveIntensity", 1)) - er, eg, eb, _ = hex_to_linear_rgba(em) - emissive_factor = [er * ei, eg * ei, eb * ei] - - pbr = PBRMaterial( - baseColorFactor=list(color), - metallicFactor=metal, - roughnessFactor=rough, - emissiveFactor=emissive_factor, - alphaMode="BLEND" if opacity < 1 else "OPAQUE", - name="mat", - ) - mesh.visual = trimesh.visual.TextureVisuals(material=pbr) - - -def slug(s: str) -> str: - return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-") or "object" - - -# ── Main ───────────────────────────────────────────────────────────────────── - - -def asset_geometry_hash(prims: list) -> str: - """Hash the geometry-defining parts of an asset's primitives (no top-level transform). - Two assets with identical sub-primitives share one GLB.""" - canonical = [] - for p in prims: - canonical.append({ - "type": p.get("type"), - "dimensions": p.get("dimensions"), - "transform": p.get("transform"), - "material": p.get("material", {}).get("color"), # color only — ignore textures - }) - return hashlib.sha256(json.dumps(canonical, sort_keys=True).encode()).hexdigest()[:12] - - -def main(apt_path: str) -> None: - with open(apt_path) as f: - doc = json.load(f) - - GLB_DIR.mkdir(parents=True, exist_ok=True) - - # Group assets by geometry hash so duplicates share one GLB - seen_hashes: dict[str, str] = {} # hash → glb_filename - placements: list[dict] = [] - title_counts: dict[str, int] = {} - - assets = [a for a in doc["assets"] if not a.get("_deltaOnly") and a.get("states")] - print(f"Processing {len(assets)} assets...") - - for asset in assets: - current_id = asset.get("currentStateId", "state-default") - state = next( - (s for s in asset["states"] if s.get("id") == current_id), - asset["states"][0], - ) - prims = state.get("scene", {}).get("primitives", []) - if not prims: - continue - - ghash = asset_geometry_hash(prims) - if ghash not in seen_hashes: - title = asset.get("title") or asset.get("libraryAssetId") or "object" - base_slug = slug(title) - count = title_counts.get(base_slug, 0) - title_counts[base_slug] = count + 1 - slug_name = base_slug if count == 0 else f"{base_slug}-{count + 1}" - glb_name = f"{slug_name}.glb" - - built = [] - for prim in prims: - geom = make_geom(prim) - if geom is None: - continue - apply_material(geom, prim.get("material", {})) - apply_transform(geom, prim.get("transform", {})) - built.append(geom) - - if not built: - print(f" SKIP {title} — no geometry") - continue - - # Match engine's instantiateAsset: recenter around the bbox center. - # asset.transform.position is where this center lands in world space. - all_verts = np.concatenate([g.vertices for g in built]) - bbox_center = (all_verts.min(axis=0) + all_verts.max(axis=0)) / 2.0 - recenter = trimesh.transformations.translation_matrix(-bbox_center) - scene = trimesh.Scene() - for g in built: - g.apply_transform(recenter) - scene.add_geometry(g) - - out_path = GLB_DIR / glb_name - scene.export(str(out_path)) - seen_hashes[ghash] = glb_name - print(f" WROTE {glb_name} ({out_path.stat().st_size / 1024:.1f} KB, {len(built)} parts, recenter={bbox_center.round(3).tolist()})") - - placements.append({ - "file": seen_hashes[ghash], - "transform": asset.get("transform", {}), - "title": asset.get("title") or "object", - }) - - # Emit placements file - emit_placements_js(placements, OUT / "objects.js") - print(f"\n{len(seen_hashes)} unique GLBs, {len(placements)} placements") - total_glb_bytes = sum(p.stat().st_size for p in GLB_DIR.iterdir()) - print(f"objects/ total: {total_glb_bytes / 1024:.1f} KB") - - -def emit_placements_js(placements: list, out_path: Path) -> None: - """Emit objects.js — placements list that index.js consumes via loadGLTF.""" - def jnum(x: float, ndigits: int = 4) -> str: - if x == int(x): - return str(int(x)) - s = f"{x:.{ndigits}f}".rstrip("0").rstrip(".") - return s if s else "0" - - def emit_transform(tr: dict) -> str: - pos = tr.get("position", {}) - rot = tr.get("rotation", {}) - scale = tr.get("scale", {}) - parts = [f"p:[{jnum(pos.get(k, 0))}" for k in "xyz"] - p_arr = "[" + ",".join(jnum(pos.get(k, 0)) for k in "xyz") + "]" - result = [f"p:{p_arr}"] - if any(rot.get(k, 0) != 0 for k in "xyz"): - r_arr = "[" + ",".join(jnum(rot.get(k, 0)) for k in "xyz") + "]" - result.append(f"r:{r_arr}") - if any(scale.get(k, 1) != 1 for k in "xyz"): - s_arr = "[" + ",".join(jnum(scale.get(k, 1)) for k in "xyz") + "]" - result.append(f"s:{s_arr}") - return "{" + ", ".join(result) + "}" - - lines = [ - "// Asset placements — pairs each loadable GLB in ./objects/ with a world transform.", - "// To add furniture: drop a .glb into objects/, append an entry here.", - "", - "export const OBJECTS = [", - ] - for p in placements: - title = json.dumps(p["title"]) - lines.append(f" {{ file: '{p['file']}', t: {emit_transform(p['transform'])}, title: {title} }},") - lines += ["];", ""] - out_path.write_text("\n".join(lines)) - - -if __name__ == "__main__": - ap = argparse.ArgumentParser() - ap.add_argument("apt_path", help="path to apt.json") - args = ap.parse_args() - main(args.apt_path) diff --git a/misc/DimSim/scripts/package-release.sh b/misc/DimSim/scripts/package-release.sh deleted file mode 100755 index f4f271a494..0000000000 --- a/misc/DimSim/scripts/package-release.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Creates release artifacts for DimSim distribution. -# dimsim-core-v{VERSION}.tar.gz — frontend bundle (no scenes) -# scene-apt-v{VERSION}.tar.gz — apt scene (gzipped JSON) - -cd "$(dirname "$0")/.." - -VERSION=${1:-"0.1.0"} - -echo "Packaging DimSim v${VERSION}..." - -# Verify dist/ exists -if [ ! -f "dist/index.html" ]; then - echo "Error: dist/ not found. Run 'npm run build' first." - exit 1 -fi - -# Core: everything in dist/ except the huge apt scene jsons (those ship as -# scene-apt-v{VERSION}.tar.gz). empty.json + manifest.json are tiny and -# travel in core so authoring workflows (DIMSIM_SCENE=empty) work out of -# the box. -echo "Packaging core assets..." -tar -czf "dimsim-core-v${VERSION}.tar.gz" \ - -C dist \ - --exclude='sims/apt.json' \ - --exclude='sims/apt_.json' \ - . - -echo "Packaging apt scene..." -gzip -c dist/sims/apt.json > "scene-apt-v${VERSION}.tar.gz" - -echo "Packaging evals..." -tar -czf "dimsim-evals-v${VERSION}.tar.gz" \ - -C evals \ - . - -echo "" -echo "Release artifacts:" -ls -lh "dimsim-core-v${VERSION}.tar.gz" "scene-apt-v${VERSION}.tar.gz" "dimsim-evals-v${VERSION}.tar.gz" - -echo "" -echo "Upload to GitHub Release:" -echo " gh release create v${VERSION} dimsim-core-v${VERSION}.tar.gz scene-apt-v${VERSION}.tar.gz dimsim-evals-v${VERSION}.tar.gz" diff --git a/misc/DimSim/server.js b/misc/DimSim/server.js deleted file mode 100644 index 9e45ee4663..0000000000 --- a/misc/DimSim/server.js +++ /dev/null @@ -1,174 +0,0 @@ -import express from "express"; -import cors from "cors"; -import OpenAI from "openai"; -import { createHash } from "crypto"; -import { readFileSync, writeFileSync, existsSync } from "fs"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// ── API Keys (from environment variables) ─────────────────────────────────── -const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ""; -const GEMINI_API_KEY = process.env.GEMINI_API_KEY || ""; -const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"; -const VLM_MIN_INTERVAL_MS = 900; -const VLM_MAX_RETRIES = 4; -let _lastVlmAt = 0; - -// ── Clients ───────────────────────────────────────────────────────────────── -function getClient(model) { - if (model.startsWith("gemini")) { - return new OpenAI({ apiKey: GEMINI_API_KEY, baseURL: GEMINI_BASE_URL }); - } - return new OpenAI({ apiKey: OPENAI_API_KEY }); -} - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function paceVlmRequests() { - const now = Date.now(); - const waitMs = Math.max(0, _lastVlmAt + VLM_MIN_INTERVAL_MS - now); - if (waitMs > 0) await sleep(waitMs); - _lastVlmAt = Date.now(); -} - -async function requestWithRetry(runRequest) { - let attempt = 0; - let lastErr = null; - while (attempt < VLM_MAX_RETRIES) { - try { - await paceVlmRequests(); - return await runRequest(); - } catch (err) { - lastErr = err; - const status = Number(err?.status || err?.statusCode || 0); - const retryAfterHeader = - Number(err?.headers?.["retry-after"] || err?.response?.headers?.get?.("retry-after") || 0) || 0; - const shouldRetry = status === 429 || status === 503 || status === 504; - attempt += 1; - if (!shouldRetry || attempt >= VLM_MAX_RETRIES) break; - const backoff = Math.min(6000, 600 * 2 ** (attempt - 1)); - const retryAfterMs = retryAfterHeader > 0 ? retryAfterHeader * 1000 : 0; - const jitterMs = Math.floor(Math.random() * 250); - await sleep(Math.max(backoff, retryAfterMs) + jitterMs); - } - } - throw lastErr; -} - -// ── Asset library file ────────────────────────────────────────────────────── -const ASSET_LIBRARY_FILE = join(__dirname, "vlm-server", "asset-library.json"); - -// ── App ───────────────────────────────────────────────────────────────────── -const app = express(); -app.use(cors()); -app.use(express.json({ limit: "100mb" })); - -// ── POST /vlm/decision ────────────────────────────────────────────────────── -app.post("/vlm/decision", async (req, res) => { - try { - const { model, prompt, imageBase64, context, messages: reqMessages, max_tokens } = req.body; - const client = getClient(model); - - let messages = reqMessages; - if (!messages) { - const userContent = imageBase64 - ? [ - { type: "text", text: `Context:\n${JSON.stringify(context || {})}` }, - { type: "image_url", image_url: { url: `data:image/jpeg;base64,${imageBase64}` } }, - ] - : `Context:\n${JSON.stringify(context || {})}`; - messages = [ - { role: "system", content: prompt || "You are an AI agent. Output JSON only." }, - { role: "user", content: userContent }, - ]; - } - - const maxTok = max_tokens || 16384; - const params = { model, messages, temperature: 0.3 }; - if (model.startsWith("gemini")) { - params.max_tokens = maxTok; - } else { - params.max_completion_tokens = maxTok; - } - - const response = await requestWithRetry(() => client.chat.completions.create(params)); - const text = response.choices?.[0]?.message?.content || ""; - res.json({ raw: text }); - } catch (err) { - const status = Number(err?.status || err?.statusCode || 500); - const safeStatus = Number.isFinite(status) && status >= 400 && status <= 599 ? status : 500; - const msg = err?.message || "VLM request failed"; - console.error("[/vlm/decision]", safeStatus, msg); - res.status(safeStatus).json({ detail: msg }); - } -}); - -// ── POST /vlm/generate-image ──────────────────────────────────────────────── -app.post("/vlm/generate-image", async (req, res) => { - try { - const { prompt, size = "1024x1024", quality = "medium" } = req.body; - const client = new OpenAI({ apiKey: OPENAI_API_KEY }); - const fullPrompt = `Seamless tileable texture for 3D rendering, top-down flat view, no perspective: ${prompt}`; - - let lastError = null; - for (const modelName of ["gpt-image-1-mini", "gpt-image-1"]) { - try { - const response = await client.images.generate({ model: modelName, prompt: fullPrompt, size, quality, n: 1 }); - const b64 = response.data?.[0]?.b64_json; - if (b64) return res.json({ b64, dataUrl: `data:image/png;base64,${b64}`, model: modelName }); - } catch (err) { - lastError = err.message; - continue; - } - } - - const hash = createHash("sha256").update(fullPrompt).digest("hex"); - const h = parseInt(hash.slice(0, 8), 16); - const hue = h % 360; - const hue2 = (hue + 34) % 360; - const svg = ` - - -`; - res.json({ b64: null, dataUrl: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg), fallback: true, error: lastError }); - } catch (err) { - console.error("[/vlm/generate-image]", err.message); - res.status(500).json({ detail: err.message }); - } -}); - -// ── GET /vlm/asset-library ────────────────────────────────────────────────── -app.get("/vlm/asset-library", (req, res) => { - try { - if (existsSync(ASSET_LIBRARY_FILE)) { - const data = JSON.parse(readFileSync(ASSET_LIBRARY_FILE, "utf-8")); - if (Array.isArray(data)) return res.json({ assets: data }); - } - } catch (err) { - console.error("[asset-library] read failed:", err.message); - } - res.json({ assets: [] }); -}); - -// ── POST /vlm/asset-library ───────────────────────────────────────────────── -app.post("/vlm/asset-library", (req, res) => { - try { - const { assets } = req.body; - writeFileSync(ASSET_LIBRARY_FILE, JSON.stringify(assets), "utf-8"); - res.json({ ok: true, count: assets?.length || 0 }); - } catch (err) { - console.error("[asset-library] write failed:", err.message); - res.status(500).json({ detail: err.message }); - } -}); - -// ── Start ─────────────────────────────────────────────────────────────────── -const PORT = process.env.PORT || 8000; -app.listen(PORT, "127.0.0.1", () => { - console.log(`DimSim VLM server running on http://127.0.0.1:${PORT}`); -}); diff --git a/misc/DimSim/update-sims.sh b/misc/DimSim/update-sims.sh deleted file mode 100644 index a672030781..0000000000 --- a/misc/DimSim/update-sims.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Regenerate public/sims/manifest.json from the .json files in public/sims/. -# Run after adding or removing scene files. -set -e -DIR="$(cd "$(dirname "$0")" && pwd)/public/sims" -cd "$DIR" -echo -n "[" > manifest.json -first=true -for f in *.json; do - [ "$f" = "manifest.json" ] && continue - name="${f%.json}" - if [ "$first" = true ]; then first=false; else echo -n "," >> manifest.json; fi - echo -n "\"$name\"" >> manifest.json -done -echo "]" >> manifest.json -echo "Updated manifest: $(cat manifest.json)" From bbde1c879c583ae5cedc443c9da09d2898735062 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:14:57 -0700 Subject: [PATCH 23/43] docs: rewrite scenes.md as pure how-to + drop architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scenes.md: refocused on the "create/edit a scene" loop. Concrete recipe up top, `api` table, common patterns (loops, GLBs, lighting, shadow cam tuning), copy-from-empty tip. No backstory. - getting-started.md: dropped the apt.json regeneration aside — not relevant for someone arriving fresh. - evals.md: trimmed the runtime-resolution explainer; now purely "here's how to author an eval". - architecture.md: deleted. Anyone interested in the internals can read the code. - README.md: docs index trimmed to three entries. No code touched. --- misc/DimSim/README.md | 9 +- misc/DimSim/docs/architecture.md | 118 ---------------------- misc/DimSim/docs/evals.md | 130 +++++++++++------------- misc/DimSim/docs/getting-started.md | 85 +++++++--------- misc/DimSim/docs/scenes.md | 151 +++++++++++++++++++--------- 5 files changed, 203 insertions(+), 290 deletions(-) delete mode 100644 misc/DimSim/docs/architecture.md diff --git a/misc/DimSim/README.md b/misc/DimSim/README.md index 0644a28022..4698d59f9a 100644 --- a/misc/DimSim/README.md +++ b/misc/DimSim/README.md @@ -22,12 +22,11 @@ cd On first run, `cli/cli.ts` will build `dist/` via Vite (dimsim ships its frontend as source — Deno+Vite materializes it in ~20s). -## Authoring +## Docs -- New scenes: see [docs/scenes.md](docs/scenes.md) -- New evals: see [docs/evals.md](docs/evals.md) -- Architecture overview: see [docs/architecture.md](docs/architecture.md) -- Tour: see [docs/getting-started.md](docs/getting-started.md) +- [docs/getting-started.md](docs/getting-started.md) — 5-minute tour +- [docs/scenes.md](docs/scenes.md) — create + edit scenes +- [docs/evals.md](docs/evals.md) — write eval workflows ## Install the CLI (optional) diff --git a/misc/DimSim/docs/architecture.md b/misc/DimSim/docs/architecture.md deleted file mode 100644 index 307a4ac399..0000000000 --- a/misc/DimSim/docs/architecture.md +++ /dev/null @@ -1,118 +0,0 @@ -# DimSim architecture - -``` - dimos (Python) - │ - │ LCM/UDP + control WS - ▼ - ┌───────────────────────────────────────┐ - │ Bridge (Deno, cli/bridge/) │ - │ • LCM multicast (/cmd_vel, /odom, │ - │ /color_image, /lidar/points) │ - │ • Server-side Rapier physics step │ - │ • WS relay to browser │ - └─────────────────┬─────────────────────┘ - │ WebSocket (control + sensors) - ▼ - ┌───────────────────────────────────────┐ - │ Browser engine (vite-bundled, │ - │ src/engine.js + friends) │ - │ • Three.js scene + agent visual │ - │ • Browser-side Rapier (display) │ - │ • Loads scenes//index.js │ - │ • Loads scenes//evals/*.js │ - │ (when triggered) │ - └───────────────────────────────────────┘ -``` - -## File layout - -``` -misc/DimSim/ - src/ ← vite-bundled browser code - engine.js - AiAvatar.js # agent visual + capsule colliders - main.js / style.css # entry + UI - bridge.ts # browser WS client to the bridge - sceneApi.ts # the API scenes import from - sceneEditor.ts # runtime exec sandbox (for SceneClient SDK) - - cli/ ← Deno-only code (not bundled) - cli.ts # `dimsim` CLI entrypoint - deno.json # JSR import map + tasks - bridge/server.ts # the bridge server itself - bridge/lidar.ts # server-side ray-cast lidar - bridge/physics.ts # server-side Rapier step - headless/launcher.ts # Playwright bootstrap - vendor/lcm/* # vendored @dimos/lcm w/ macOS multicast fix - test/dimos_integration.py # LCM smoke test - agent.py # dimos Python agent runner - - evals/ ← eval system, both runtimes - harness.ts # browser orchestrator (bundled → dimsim-eval.js) - rubrics.ts # objectDistance, radiusContains, helpers - runner.ts # Deno runner — walks scenes/, dispatches via WS - deno-client.ts # Deno `@dimsim/eval` (for direct deno-run) - deno.json # @std/path import map + Deno LSP hint - - scenes/ ← user-authored scenes - / - index.js # default-exports an async build({...}) function - data/, textures/ # optional, scene-specific - evals/.js # workflow files (importable in both browser + Deno) - deno.json # @dimsim/eval → ../evals/deno-client.ts - - public/ # static assets, copied by vite - agent-model/dimsim_unitree_stub.glb - logo.svg - - index.html # boot html (carries the importmap) - vite.config.js # pins evals/harness.ts → dist/assets/dimsim-eval.js - package.json # vite + three + rapier + spark (browser only) -``` - -## Key contracts - -**Bridge ↔ browser** — WebSockets on port 8090: - -- `/?ch=control` — text JSON commands (runEval, embodimentConfig, exec, …) + LCM binary frames -- `/?ch=sensors` — sensor publishes (lidar, snapshots) -- `/?ch=rgb`, `/?ch=depth` — image streams - -**Bridge ↔ dimos** — LCM multicast at `239.255.76.67:7667`: - -- `/cmd_vel#geometry_msgs.Twist` — in -- `/odom#geometry_msgs.PoseStamped` — out -- `/color_image#sensor_msgs.Image` — out -- `/lidar/points#sensor_msgs.PointCloud2` — out - -**Scene module** — `scenes//index.js`: - -```js -export default async function build(api) { - const { scene, THREE, physics, setSky, loadGLTF, loadLevel } = api; - // … construct meshes, add colliders, set lighting … - return { embodiment: null, spawnPoint: { x: 0, y: 0.5, z: 0 } }; -} -``` - -**Eval workflow** — `scenes//evals/.js`: - -```js -import { runEval } from '@dimsim/eval'; -await runEval({ - scene: '', - task: 'human-readable description', - timeoutSec: 30, - startPose: { x: 0, y: 0.5, z: 0, yaw: 0 }, - setup: async (ctx) => { /* optional */ }, - success: (ctx) => ctx.rubrics.objectDistance({ target: 'X', thresholdM: 2.0 }), -}); -``` - -The `@dimsim/eval` specifier resolves two ways via two import maps: - -- **Browser** (`index.html`) → `/assets/dimsim-eval.js` (bundled harness chunk, pinned by vite.config.js) -- **Deno** (`scenes/deno.json`) → `evals/deno-client.ts` (dispatches to the bridge via WS) - -Same workflow file, two runtimes. diff --git a/misc/DimSim/docs/evals.md b/misc/DimSim/docs/evals.md index 7a4de47554..43782f13e8 100644 --- a/misc/DimSim/docs/evals.md +++ b/misc/DimSim/docs/evals.md @@ -1,109 +1,95 @@ -# Authoring evals +# Evals -An eval workflow is a JS file at `scenes//evals/.js` that imports `runEval` from `@dimsim/eval` and calls it. The file is a runnable program — same shape whether the browser is executing it (when triggered from the CLI / runner) or Deno is (when you `deno run` the file directly to dispatch it at a running bridge). +An eval workflow is one JS file at `scenes//evals/.js`. It imports `runEval` from `@dimsim/eval` and calls it. That's the whole authoring surface. -## Minimal workflow +## Create a new eval ```js // scenes/apartment/evals/go-to-couch.js import { runEval } from '@dimsim/eval'; await runEval({ - scene: 'apartment', - task: 'Go to the couch', - timeoutSec: 30, - startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, - success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional', thresholdM: 2.0 }), + scene: 'apartment', + task: 'Go to the couch', + timeoutSec: 30, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + success: (ctx) => ctx.rubrics.objectDistance({ target: 'sectional', thresholdM: 2.0 }), }); ``` -That's it. Drop the file under any scene's `evals/` folder and `dimsim eval list` will pick it up. +Drop the file under any scene's `evals/` folder and `dimsim eval list` picks it up. + +## Run it + +```bash +dimsim eval go-to-couch # against the open sim +dimsim eval --headless --scene apartment --workflow go-to-couch # standalone / CI +deno run -A misc/DimSim/scenes/apartment/evals/go-to-couch.js # direct execution +``` + +All three end up at the same harness in the browser; pick whichever feels natural for the moment. ## The workflow object | Field | Required | Description | |---|---|---| -| `scene` | ✓ | Scene name. Must match the directory under `scenes/`. | -| `task` | ✓ | Human-readable goal description. Shown in the overlay + logged. | -| `success(ctx)` | ✓ | Function returning `{ passed: bool, reason?, score? }`. Called every 250 ms until it passes or timeout. | -| `timeoutSec` | – | Default 120. Max wall-clock seconds before forced fail. | -| `startPose` | – | `{x, y, z, yaw?}` (yaw in degrees). Applied to the agent before `setup`. | -| `setup(ctx)` | – | Optional async fn that runs once at start. Spawn obstacles, configure embodiments, etc. | +| `scene` | ✓ | Scene name. Must match a directory under `scenes/`. | +| `task` | ✓ | Human-readable goal. Shown in the overlay + logged. | +| `success(ctx)` | ✓ | Returns `{passed, reason?, score?}`. Polled every 250 ms until it passes or timeout. | +| `timeoutSec` | – | Default 120. Wall-clock cap. | +| `startPose` | – | `{x, y, z, yaw?}` — applied before `setup`. Yaw in degrees. | +| `setup(ctx)` | – | Async fn run once at start — spawn obstacles, set props, anything. | ## The `ctx` object -`setup(ctx)` and `success(ctx)` both receive: +Both `setup(ctx)` and `success(ctx)` receive: -| Field | Description | +| Field | What | |---|---| -| `ctx.agent` | The live AiAvatar — has `setPosition`, `getPosition`, `group`, etc. | -| `ctx.agentPos` | `{x, y, z}` — convenience copy of the current agent translation | -| `ctx.sceneState` | `{ assets: AssetEntry[], agentPos }` — for rubric scoring | -| `ctx.setAgentPose({x, y, z, yaw?})` | Teleport the agent (no-op if no agent) | -| `ctx.findAsset(query)` | Case-insensitive search by title or id; returns an AssetEntry or null | -| `ctx.dist(a, b)` | Euclidean distance helper | -| `ctx.rubrics.objectDistance({target, thresholdM?})` | Built-in rubric — bbox-surface distance from agent to target asset | -| `ctx.rubrics.radiusContains({targets[], radiusM?})` | Pass if agent is within `radiusM` of the targets' centroid | +| `ctx.agent` | The live agent — `setPosition`, `getPosition`, `group`, etc. | +| `ctx.agentPos` | `{x, y, z}` — current translation, convenience copy. | +| `ctx.sceneState` | `{assets, agentPos}` — used by rubric helpers. | +| `ctx.setAgentPose({x, y, z, yaw?})` | Teleport the agent. | +| `ctx.findAsset(query)` | Case-insensitive search by title or id. | +| `ctx.dist(a, b)` | Euclidean distance. | +| `ctx.rubrics.objectDistance({target, thresholdM?})` | Pass if agent is within `thresholdM` of `target`'s bbox surface. | +| `ctx.rubrics.radiusContains({targets, radiusM?})` | Pass if agent is within `radiusM` of the centroid of `targets`. | + +## Custom scoring -You can write `success` inline if neither built-in rubric fits: +If neither built-in rubric fits, write the logic inline: ```js -success: ({ agent, findAsset, dist }) => { - const tv = findAsset('television'); +success: ({ agentPos, findAsset, dist }) => { + const tv = findAsset('television'); const couch = findAsset('sectional'); if (!tv || !couch) return { passed: false, reason: 'targets missing' }; - const between = { x: (tv.transform.x + couch.transform.x) / 2, y: 0, z: (tv.transform.z + couch.transform.z) / 2 }; - const d = dist(agent.getPosition?.()?.[0] ? { x: agent.getPosition()[0], y: 0, z: agent.getPosition()[2] } : {x:0,y:0,z:0}, between); + const mid = { + x: (tv.transform.x + couch.transform.x) / 2, + y: 0, + z: (tv.transform.z + couch.transform.z) / 2, + }; + const d = dist(agentPos, mid); return { passed: d <= 1.5, score: d, reason: `${d.toFixed(2)}m from midpoint` }; } ``` -## Three ways to run a workflow - -1. **From the CLI against an already-running sim** (most common during dev): - - ```bash - dimsim eval go-to-couch # bare name - dimsim eval apartment/go-to-couch # scene-qualified - ``` - - Auto-implies `--connect`. The sim has to be open already (`dimos --simulation dimsim ...` or `dimsim dev`). - -2. **Directly via `deno run`** — runs the workflow file as a Deno program; dispatches to whichever bridge is on `localhost:8090`: - - ```bash - deno run -A misc/DimSim/scenes/apartment/evals/go-to-couch.js - ``` +## Scripted setup - Functionally identical to `dimsim eval go-to-couch`. Useful if you want to drop the `dimsim` global install. +`setup(ctx)` is async — do whatever you need before scoring starts: -3. **Headless / CI**: - - ```bash - dimsim eval --headless --scene apartment --workflow go-to-couch - dimsim eval --headless # runs all workflows for all scenes - dimsim eval --headless --output junit > junit.xml - ``` - - Spins up its own bridge and a headless Chromium, no dimos involvement. Designed for CI. - -## How the same file runs in two runtimes - -``` -import { runEval } from '@dimsim/eval'; +```js +setup: async ({ agent }) => { + agent.setPosition(-3, 0.5, 0); + await new Promise(r => setTimeout(r, 250)); // let physics settle +}, ``` -| Runtime | `@dimsim/eval` resolves to | `runEval(workflow)` does | -|---|---|---| -| **Browser** (via `index.html` importmap) | `/assets/dimsim-eval.js` (bundled harness chunk) | Sets up the eval in-place — runs `setup`, polls `success`, returns when passed or timeout | -| **Deno** (via `scenes/deno.json` importmap) | `evals/deno-client.ts` | Computes the workflow URL from `Deno.mainModule`, opens a WS to `localhost:8090`, sends `{type:'runEval', workflowUrl}`, awaits the result, exits | - -The `success` callback never actually runs in Deno — Deno only ships the URL. The browser re-imports the same file URL and executes the callbacks there. +You can spawn obstacles, change embodiments mid-eval, or set up multi-stage tests here. The harness doesn't constrain you. ## Tips -- **Set `timeoutSec` higher when running on CPU / SwiftShader** — agent setup can take 5–10s on slow renders. -- **Use a `--connect` run first** to confirm the eval logic works against your live sim. Once green, set up CI with `--headless`. -- **`startPose` is yaw-in-degrees**, not radians. `yaw: 90` looks along +X. -- **Score is yours to define.** Lower-is-better for distance-style rubrics, higher-is-better for coverage-style. CI consumers should not assume a meaning. -- **One eval at a time.** The harness is a singleton — running two evals concurrently isn't supported. Use `--parallel N` with multiple browser pages if you need throughput. +- **One eval at a time.** The harness is a singleton; running two evals concurrently isn't supported. Use `--parallel N` with multiple browser pages for throughput. +- **Score is yours to define.** Lower-is-better for distances, higher-is-better for coverage — CI consumers should not assume. +- **`startPose` yaw is in degrees**, not radians. +- **`setup`/`success` callbacks can use any browser API** (THREE, scene, Rapier) — they run in the browser context, not in Deno. diff --git a/misc/DimSim/docs/getting-started.md b/misc/DimSim/docs/getting-started.md index 734e0abd26..d9a294917d 100644 --- a/misc/DimSim/docs/getting-started.md +++ b/misc/DimSim/docs/getting-started.md @@ -1,60 +1,57 @@ # Getting started -This is the 5-minute tour. For deeper guides see [scenes.md](scenes.md) and [evals.md](evals.md). +5-minute tour. For deeper guides see [scenes.md](scenes.md) and [evals.md](evals.md). ## The two ways DimSim runs -``` -dimos drives DimSim (production / agentic / CI) - .venv/bin/dimos --simulation dimsim --dimsim-scene=apartment run unitree-go2-agentic +```bash +# 1. dimos drives DimSim (production / agentic / CI) +.venv/bin/dimos --simulation dimsim --dimsim-scene=apartment run unitree-go2-agentic -DimSim runs standalone (engine dev / scene authoring) - cd misc/DimSim/cli - deno run -A --unstable-net cli.ts dev --scene apartment +# 2. DimSim runs standalone (engine dev / scene authoring) +cd misc/DimSim/cli +deno run -A --unstable-net cli.ts dev --scene apartment ``` -Both end up with a Vite-built `dist/` and a bridge on port 8090. Both serve the same scenes. Difference is just who's driving the agent. +Both end up with a Vite-built `dist/` and a bridge on port 8090. Same scenes, same browser. Difference is just who's driving the agent. -If you have `dimsim` installed globally: +If you install the CLI globally, replace the `deno run` boilerplate with `dimsim`: ```bash cd misc/DimSim/cli deno install -gAf --unstable-net --name=dimsim --config=./deno.json ./cli.ts ``` -…you can replace the `deno run -A --unstable-net cli.ts` boilerplate with just `dimsim`. - -## A 60-second loop: edit a scene and see it +## 60-second loop: edit a scene -1. `dimos --simulation dimsim --dimsim-scene=warehouse --no-dimsim-headless run unitree-go2-basic` -2. Wait for the browser to open and the agent to spawn. -3. Open `misc/DimSim/scenes/warehouse/index.js` in your editor. -4. Change `setSky({ brightness: 0.7 })` to `setSky({ brightness: 1.5 })`. Save. -5. The browser HMR-reloads — no full refresh needed. Brighter sky. +1. `dimsim dev --scene warehouse` +2. Open `misc/DimSim/scenes/warehouse/index.js` in your editor. +3. Change `setSky({ brightness: 0.7 })` to `setSky({ brightness: 1.5 })`. Save. +4. The browser HMR-reloads — brighter sky. -## A 60-second loop: write a new eval +## 60-second loop: write an eval -1. Create `misc/DimSim/scenes/apartment/evals/look-at-window.js`: +`misc/DimSim/scenes/apartment/evals/look-at-window.js`: - ```js - import { runEval } from '@dimsim/eval'; +```js +import { runEval } from '@dimsim/eval'; - await runEval({ - scene: 'apartment', - task: 'Stand near a window', - timeoutSec: 20, - startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, - success: (ctx) => ctx.rubrics.objectDistance({ target: 'window', thresholdM: 1.5 }), - }); - ``` +await runEval({ + scene: 'apartment', + task: 'Stand near a window', + timeoutSec: 20, + startPose: { x: 0, y: 0.5, z: 3, yaw: 0 }, + success: (ctx) => ctx.rubrics.objectDistance({ target: 'window', thresholdM: 1.5 }), +}); +``` -2. From the sim that's already running: +From a separate terminal (while the sim is running): - ```bash - dimsim eval look-at-window - ``` +```bash +dimsim eval look-at-window +``` - You'll see a green/red overlay in the browser when it finishes, plus the result echoed in your terminal. +The browser shows a green/red overlay when it finishes, and the result echoes in your terminal. ## Where things live @@ -67,8 +64,6 @@ public/ static assets (default robot GLB) docs/ guides ``` -See [architecture.md](architecture.md) for the full tree + key contracts. - ## Quick reference | What | How | @@ -79,25 +74,15 @@ See [architecture.md](architecture.md) for the full tree + key contracts. | Run one eval against open sim | `dimsim eval ` | | Run one eval headless | `dimsim eval --headless --scene --workflow ` | | Run all evals for a scene | `dimsim eval --headless --scene ` | -| Run all evals, JUnit XML out | `dimsim eval --headless --output junit > junit.xml` | +| Run all evals, JUnit XML | `dimsim eval --headless --output junit > junit.xml` | | Direct workflow execution | `deno run -A scenes//evals/.js` | | Build the frontend manually | `cd misc/DimSim && npm run build` | | Spin up a profiler | `bash scripts/profile-live.sh` | | Verify cmd_vel → odom round-trip | `python cli/test/dimos_integration.py` | -## What about apt.json? - -The old apartment scene used to ship as one 97 MB `apt.json`. It was decomposed into JS-authored modules under `scenes/apartment/data/` plus a `textures/` folder. If you want to regenerate it from an apt.json blob you have lying around: - -```bash -python scripts/extract_apt_to_js.py /path/to/apt.json -``` - -The output goes back into `scenes/apartment/data/` and `scenes/apartment/textures/`. - ## Common gotchas -- **Headless browser slow to boot** on CI — use `--render cpu` and bump `--timeout 120000`. -- **macOS multicast loopback** — DimSim ships a vendored `@dimos/lcm` with a `setLoopback(true)` patch. The JSR-published version doesn't have it; dropping the vendor will break dimos↔DimSim LCM on macOS until the upstream lands the fix. -- **`unitree-go2-basic` hides lidar in Rerun** — that blueprint sets `world/lidar` visible=false in its override. Click the eye icon in the Rerun entity tree, or use `unitree-go2-spatial` / `unitree-go2-agentic` which leave it visible. +- **Headless slow to boot on CI** — use `--render cpu` and bump `--timeout 120000`. +- **`unitree-go2-basic` hides lidar in Rerun** by override. Click the eye icon in the Rerun entity tree, or use `unitree-go2-spatial` / `unitree-go2-agentic` which leave it visible. - **Click-to-nav** in Rerun only works on nav-enabled blueprints (`unitree-go2-agentic` and friends). `unitree-go2-basic` has no nav stack. +- **First launch is slow** — Vite builds `dist/` on first run (~20s). Subsequent runs reuse it. diff --git a/misc/DimSim/docs/scenes.md b/misc/DimSim/docs/scenes.md index 2f21a06d88..5fd5340416 100644 --- a/misc/DimSim/docs/scenes.md +++ b/misc/DimSim/docs/scenes.md @@ -1,86 +1,147 @@ -# Authoring scenes +# Scenes -A scene is a JS module at `scenes//index.js` that default-exports an async `build(api)` function. dimsim dynamically imports it at boot, hands you the scene API, and lets you do whatever you'd do in a normal Three.js script — `scene.add(mesh)`, attach lights, register physics colliders. +A scene is one JS file: `scenes//index.js`. It default-exports an async `build(api)` function. DimSim hot-reloads it on save. -## Minimal scene +## Create a new scene + +```bash +mkdir -p misc/DimSim/scenes/my-room +``` + +`misc/DimSim/scenes/my-room/index.js`: ```js -// scenes/warehouse/index.js export default async function build({ scene, THREE, physics, setSky }) { - setSky({ topColor: '#3a4654', horizonColor: '#cfd6df', brightness: 0.7 }); + setSky({ topColor: '#3a4654', horizonColor: '#cfd6df', brightness: 0.8 }); // floor const floor = new THREE.Mesh( - new THREE.BoxGeometry(30, 0.2, 40), - new THREE.MeshPhysicalMaterial({ color: 0x6b6f74, roughness: 0.95 }), + new THREE.BoxGeometry(20, 0.2, 20), + new THREE.MeshPhysicalMaterial({ color: 0x808080, roughness: 0.9 }), ); floor.position.y = -0.1; scene.add(floor); - physics.staticCollider(floor, 'box'); // make it solid for the agent + lidar + physics.staticCollider(floor, 'box'); - // sun + ambient - scene.add(new THREE.HemisphereLight(0xffffff, 0x404040, 0.8)); - const sun = new THREE.DirectionalLight(0xffffff, 1.2); + // a wall + const wall = new THREE.Mesh( + new THREE.BoxGeometry(20, 3, 0.2), + new THREE.MeshPhysicalMaterial({ color: 0xc4c1b8, roughness: 0.8 }), + ); + wall.position.set(0, 1.5, -10); + scene.add(wall); + physics.staticCollider(wall, 'box'); + + // lighting + scene.add(new THREE.HemisphereLight(0xffffff, 0x404040, 0.6)); + const sun = new THREE.DirectionalLight(0xffffff, 1.0); sun.position.set(10, 20, 10); sun.castShadow = true; scene.add(sun); - return { - embodiment: null, // dimos sends the embodiment over WS - spawnPoint: { x: 0, y: 0.5, z: 0 }, - }; + return { spawnPoint: { x: 0, y: 0.5, z: 0 } }; } ``` Run it: ```bash -dimsim dev --scene warehouse +dimsim dev --scene my-room # or via dimos: -dimos --simulation dimsim --dimsim-scene=warehouse run unitree-go2-basic +dimos --simulation dimsim --dimsim-scene=my-room run unitree-go2-basic ``` -A live example with stacked crates, pallet racks, and pendant lights is at `scenes/warehouse/index.js`. +## Edit a scene + +Open the file, edit, save. The browser HMR-reloads — no full refresh. + +The whole `build()` re-runs on every save, so iteration is cheap. Try changing `setSky({ brightness: 0.8 })` to `1.5` — the sky brightens within a second. ## The `api` argument -`build(api)` receives an object — destructure what you need: +`build(api)` gets one argument — destructure what you need: -| Field | Description | +| Field | What | |---|---| -| `scene` | `THREE.Scene` — add meshes, lights, groups here | -| `THREE` | The engine's THREE module; use this rather than re-importing | -| `physics.staticCollider(mesh, shape)` | Attach a static collider. `shape`: `"box"` / `"trimesh"` / `"sphere"` | -| `physics.dynamicCollider(mesh, {mass, shape})` | Rigid body — falls, bounces. Engine syncs its position into `mesh` each frame | -| `setSky(opts)` | `{topColor, horizonColor, bottomColor, brightness, softness, sunStrength, sunHeight}` | -| `loadGLTF(url)` | Async GLB loader. Returns `{ scene, animations, ... }` | -| `loadLevel(data)` | Feed an apt-shape blob through the engine's importLevelFromJSON — populates the assets registry so E-key pickup / multi-state objects work | -| `loadJson(url)` | Like loadLevel, but fetches a JSON file first | -| `agent`, `camera`, `renderer`, `RAPIER`, `rapierWorld` | Live references — same instances the engine uses | +| `scene` | The `THREE.Scene`. `scene.add(mesh)` anything you want rendered. | +| `THREE` | The engine's THREE module. Use this rather than re-importing. | +| `physics.staticCollider(mesh, shape)` | Make a mesh solid (agent can't walk through, lidar hits it). `shape`: `'box'` \| `'trimesh'` \| `'sphere'`. | +| `physics.dynamicCollider(mesh, {mass, shape})` | Mesh becomes a rigid body — falls, can be pushed. Engine syncs `mesh.position` each frame. | +| `setSky({...})` | Atmosphere. Keys: `topColor`, `horizonColor`, `bottomColor`, `brightness`, `softness`, `sunStrength`, `sunHeight`. | +| `loadGLTF(url)` | Async GLB load — `await loadGLTF('./forklift.glb')` returns `{scene, animations, …}`. | +| `agent`, `camera`, `renderer`, `RAPIER`, `rapierWorld` | Live engine refs if you need them. | + +## Return value + +`build()` can return `{ spawnPoint?, embodiment? }`: + +```js +return { spawnPoint: { x: 2, y: 0.5, z: -5 } }; +``` + +If omitted, the agent spawns at `(2, 0.5, 3)`. + +## Common patterns + +**Loops for repeated geometry.** The whole module re-runs on HMR, so spawning 50 crates via a loop is fine — no `_disposed` bookkeeping needed. -You can also `import * as THREE from 'three'` if you prefer the explicit form, but the `api.THREE` is guaranteed to be the same instance the engine uses (avoiding "two THREEs" bugs). +```js +const crateGeo = new THREE.BoxGeometry(1, 1, 1); +const crateMat = new THREE.MeshPhysicalMaterial({ color: 0x8b5a2b, roughness: 0.75 }); +for (let i = 0; i < 8; i++) { + const c = new THREE.Mesh(crateGeo, crateMat); + c.position.set((i % 4) - 1.5, 0.5 + Math.floor(i / 4), 3); + scene.add(c); + physics.staticCollider(c, 'box'); +} +``` + +**GLB props.** Drop a GLB next to `index.js`, then: + +```js +const gltf = await loadGLTF('./forklift.glb'); +const forklift = gltf.scene; +forklift.position.set(5, 0, 0); +scene.add(forklift); +physics.staticCollider(forklift, 'trimesh'); +``` + +**Lighting** — `HemisphereLight` for ambient fill + one `DirectionalLight` with `castShadow = true` is enough for most interiors. `PointLight`s give nice volumetric pendant effects but turn off `castShadow` to keep frame rate sane. -## Adding interactive objects (limitation) +**Tune the shadow camera** if shadows pop: -Right now, anything you add with `scene.add(mesh)` is **invisible to the E-key interaction system**. Pickable items, multi-state cabinets, openable doors, the TV-toggle pattern — all of these live in the engine's `assets[]` registry, which is only populated by `loadLevel(data)` / `loadJson(url)` with apt-shape data. +```js +const sun = new THREE.DirectionalLight(0xffffff, 1.0); +sun.castShadow = true; +sun.shadow.mapSize.set(2048, 2048); +sun.shadow.camera.left = -20; +sun.shadow.camera.right = 20; +sun.shadow.camera.top = 25; +sun.shadow.camera.bottom = -25; +scene.add(sun); +``` + +**Materials** — `MeshPhysicalMaterial` supports clearcoat, sheen, transmission, iridescence — use it for anything you care about visually. The engine sets the renderer up for HDR/PBR. + +## Always pair `scene.add(mesh)` with a collider -Two options today: +Otherwise the agent walks through it and lidar passes straight through: -1. **Decompose your scene's interactive objects into apt-shape data** (see `scenes/apartment/data/objects.js` for the format) and feed them through `loadLevel`. Tedious but works. -2. **Hold for the `registerAsset(group, {pickable, states, actions})` API** — planned but not yet built. Will let `new THREE.Group(...)` participate in interactions without the apt-shape detour. +```js +scene.add(mesh); +physics.staticCollider(mesh, 'box'); // ← don't skip this +``` -The apartment scene (`scenes/apartment/index.js`) uses option 1 — its `data/` folder has 123 assets including 28 interactive ones. +Pure-visual meshes (decoration the agent can't bump into) — skip the collider on purpose. Just be aware. -## Hot reload +## Tip: start from `scenes/empty/` -dimsim's bridge watches `scenes//` and re-fires the engine's HMR handler on any file save. You'll see a `[bridge] hot-reload …` line in the bridge logs, then the engine logs `[dimos] hot-reloaded `. No browser refresh needed. +It's a one-file scene with a floor and a sky. Copy it as a template: -Limitation: HMR re-runs `build(api)` which re-adds everything — but the engine doesn't currently scrub interactive state from `loadLevel`. If your scene uses `loadLevel`, expect duplicate assets after HMR; refresh the browser instead. +```bash +cp -r misc/DimSim/scenes/empty misc/DimSim/scenes/my-room +``` -## Tips +## What about interactive objects? -- **Always pair `scene.add(mesh)` with `physics.staticCollider(mesh, ...)` if the agent / lidar should see it.** Visual-only meshes are fine — lidar won't hit them. -- **Use `MeshPhysicalMaterial`** for anything you care about visually — supports clearcoat, sheen, transmission, iridescence. -- **For repeating elements** (racks, pillars, crates) use loops in `build`. The whole module re-runs on HMR, so iteration is cheap. -- **Spawn point**: `spawnPoint` from `build`'s return value is where the agent gets placed at scene-load time. dimos may override it later. -- **`scenes/empty/index.js`** is the bare-minimum scene — a floor and a sky. Useful as a starting template. +Pickable items, openable doors, multi-state cabinets — these need a `registerAsset(group, {pickable, states, actions})` API that doesn't exist yet. Until then, anything you add with `scene.add` is visible/collidable but the **E-key interaction system won't see it**. The apartment scene is the only one with full E-key interactivity today. From 388d3ce5b1eaa5b5b976f6d4bfbfd201181fd779 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:15:09 -0700 Subject: [PATCH 24/43] docs(scenes): drop the interactive-objects section per request --- misc/DimSim/docs/scenes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/misc/DimSim/docs/scenes.md b/misc/DimSim/docs/scenes.md index 5fd5340416..88f61ae710 100644 --- a/misc/DimSim/docs/scenes.md +++ b/misc/DimSim/docs/scenes.md @@ -141,7 +141,3 @@ It's a one-file scene with a floor and a sky. Copy it as a template: ```bash cp -r misc/DimSim/scenes/empty misc/DimSim/scenes/my-room ``` - -## What about interactive objects? - -Pickable items, openable doors, multi-state cabinets — these need a `registerAsset(group, {pickable, states, actions})` API that doesn't exist yet. Until then, anything you add with `scene.add` is visible/collidable but the **E-key interaction system won't see it**. The apartment scene is the only one with full E-key interactivity today. From f294289d2ed3c1f198d9e9d77e0f9a3e0e2c0e2e Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:19:43 -0700 Subject: [PATCH 25/43] apartment: demonstrate threejs dev cycle on top of the loaded scene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the validation examples from dimos#1691 inline in scenes/apartment/index.js — each section maps to one of Lesh's asks: 1. loadLevel(...) the authored apartment data (unchanged) 2. THREE.SphereGeometry + MeshPhysicalMaterial + staticCollider → "Standard threejs API" + "Optional physics" + "New elements" 3. THREE.BoxGeometry crate + box collider 4. loadGLTF + physics.staticCollider(prop, 'trimesh') → "Model importing" + "Optional physics for models" 5. THREE.PointLight accent light → "add a light, reload" The editing-flow validation is the file itself: edit a `ballPos.x`, a material color, a light intensity — save — browser HMRs. All Three.js + physics interfaces in use are the same ones any new scene gets via the build() api argument; nothing apartment-specific. --- misc/DimSim/scenes/apartment/index.js | 70 ++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/misc/DimSim/scenes/apartment/index.js b/misc/DimSim/scenes/apartment/index.js index bcdfff22c3..71870e6825 100644 --- a/misc/DimSim/scenes/apartment/index.js +++ b/misc/DimSim/scenes/apartment/index.js @@ -1,18 +1,22 @@ // scenes/apartment/index.js — entry for the apartment scene. // -// JS-authored level data (apt-shape) is fed through importLevelFromJSON via -// scene-api.loadLevel(). That registers all assets in the engine's -// interaction registry, so E-key pickups, door states, and the TV toggle -// work exactly as they did when the scene loaded from apt.json directly. - -import { SKY } from './data/sky.js'; -import { TAGS } from './data/tags.js'; -import { GROUPS } from './data/groups.js'; -import { LIGHTS } from './data/lights.js'; +// First half loads the pre-authored apartment geometry (data/*.js) via the +// engine's level loader. Second half is a live demonstration of the +// standard Three.js dev cycle Lesh asked for in dimos#1691: import +// primitives, add lights, load a GLB, attach physics colliders. Save the +// file and the browser hot-reloads everything below `await loadLevel(...)`. + +import { SKY } from './data/sky.js'; +import { TAGS } from './data/tags.js'; +import { GROUPS } from './data/groups.js'; +import { LIGHTS } from './data/lights.js'; import { PRIMITIVES } from './data/structure.js'; -import { ASSETS } from './data/objects.js'; +import { ASSETS } from './data/objects.js'; + +export default async function build(api) { + const { scene, THREE, physics, loadLevel, loadGLTF } = api; -export default async function build({ loadLevel }) { + // ── 1. Load the authored apartment ────────────────────────────────── await loadLevel({ version: '2.0', worldKey: 'default', @@ -24,6 +28,50 @@ export default async function build({ loadLevel }) { sceneSettings: { sky: SKY }, }); + // ── 2. Add a red ball with collisions (Lesh: "New elements") ──────── + // Edit `ballPos` or `ballRadius` and save — HMR re-runs this block and + // the ball appears in the new spot. + const ballPos = { x: 1.5, y: 0.4, z: 0 }; + const ballRadius = 0.25; + const ball = new THREE.Mesh( + new THREE.SphereGeometry(ballRadius, 24, 24), + new THREE.MeshPhysicalMaterial({ color: 0xe53935, roughness: 0.4, metalness: 0.1 }), + ); + ball.position.set(ballPos.x, ballPos.y, ballPos.z); + ball.castShadow = ball.receiveShadow = true; + scene.add(ball); + physics.staticCollider(ball, 'sphere'); + + // ── 3. Add a wooden crate next to the couch (primitive + physics) ── + const crate = new THREE.Mesh( + new THREE.BoxGeometry(0.6, 0.6, 0.6), + new THREE.MeshPhysicalMaterial({ color: 0x8b5a2b, roughness: 0.75 }), + ); + crate.position.set(3.2, 0.3, 1.5); + crate.castShadow = crate.receiveShadow = true; + scene.add(crate); + physics.staticCollider(crate, 'box'); + + // ── 4. Load a GLB and drop it in (Lesh: "Model importing") ────────── + // Using the shipped robot stub as a sample asset. Replace with any GLB + // path under /scenes/apartment/ or any absolute URL the bridge serves. + try { + const gltf = await loadGLTF('/agent-model/dimsim_unitree_stub.glb'); + const prop = gltf.scene; + prop.position.set(-3, 0.3, 0); + prop.scale.set(0.4, 0.4, 0.4); + prop.traverse((o) => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } }); + scene.add(prop); + physics.staticCollider(prop, 'trimesh'); + } catch (e) { + console.warn('[scene] GLB load failed (ok if the file is missing):', e); + } + + // ── 5. Add an extra accent light (Lesh: "add a light, reload") ────── + const accent = new THREE.PointLight(0xff6b6b, 6, 4); + accent.position.set(1.5, 1.8, 0); + scene.add(accent); + return { embodiment: null, spawnPoint: { x: 2, y: 0.5, z: 3 }, From 6a38e6453b8f21ff48b65bee9a2ac3045d44c9e1 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:21:13 -0700 Subject: [PATCH 26/43] apartment: drop the noisy section-header comments --- misc/DimSim/scenes/apartment/index.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/misc/DimSim/scenes/apartment/index.js b/misc/DimSim/scenes/apartment/index.js index 71870e6825..04d9fea7f9 100644 --- a/misc/DimSim/scenes/apartment/index.js +++ b/misc/DimSim/scenes/apartment/index.js @@ -1,11 +1,3 @@ -// scenes/apartment/index.js — entry for the apartment scene. -// -// First half loads the pre-authored apartment geometry (data/*.js) via the -// engine's level loader. Second half is a live demonstration of the -// standard Three.js dev cycle Lesh asked for in dimos#1691: import -// primitives, add lights, load a GLB, attach physics colliders. Save the -// file and the browser hot-reloads everything below `await loadLevel(...)`. - import { SKY } from './data/sky.js'; import { TAGS } from './data/tags.js'; import { GROUPS } from './data/groups.js'; @@ -16,7 +8,6 @@ import { ASSETS } from './data/objects.js'; export default async function build(api) { const { scene, THREE, physics, loadLevel, loadGLTF } = api; - // ── 1. Load the authored apartment ────────────────────────────────── await loadLevel({ version: '2.0', worldKey: 'default', @@ -28,9 +19,6 @@ export default async function build(api) { sceneSettings: { sky: SKY }, }); - // ── 2. Add a red ball with collisions (Lesh: "New elements") ──────── - // Edit `ballPos` or `ballRadius` and save — HMR re-runs this block and - // the ball appears in the new spot. const ballPos = { x: 1.5, y: 0.4, z: 0 }; const ballRadius = 0.25; const ball = new THREE.Mesh( @@ -42,7 +30,6 @@ export default async function build(api) { scene.add(ball); physics.staticCollider(ball, 'sphere'); - // ── 3. Add a wooden crate next to the couch (primitive + physics) ── const crate = new THREE.Mesh( new THREE.BoxGeometry(0.6, 0.6, 0.6), new THREE.MeshPhysicalMaterial({ color: 0x8b5a2b, roughness: 0.75 }), @@ -52,9 +39,6 @@ export default async function build(api) { scene.add(crate); physics.staticCollider(crate, 'box'); - // ── 4. Load a GLB and drop it in (Lesh: "Model importing") ────────── - // Using the shipped robot stub as a sample asset. Replace with any GLB - // path under /scenes/apartment/ or any absolute URL the bridge serves. try { const gltf = await loadGLTF('/agent-model/dimsim_unitree_stub.glb'); const prop = gltf.scene; @@ -64,10 +48,9 @@ export default async function build(api) { scene.add(prop); physics.staticCollider(prop, 'trimesh'); } catch (e) { - console.warn('[scene] GLB load failed (ok if the file is missing):', e); + console.warn('[scene] GLB load failed:', e); } - // ── 5. Add an extra accent light (Lesh: "add a light, reload") ────── const accent = new THREE.PointLight(0xff6b6b, 6, 4); accent.position.set(1.5, 1.8, 0); scene.add(accent); From 719651c0991d8449f0b4b6a6453b0e0363f3139c Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:24:25 -0700 Subject: [PATCH 27/43] apartment: revert demo additions; back to clean loadLevel --- misc/DimSim/scenes/apartment/index.js | 40 +-------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/misc/DimSim/scenes/apartment/index.js b/misc/DimSim/scenes/apartment/index.js index 04d9fea7f9..791921941d 100644 --- a/misc/DimSim/scenes/apartment/index.js +++ b/misc/DimSim/scenes/apartment/index.js @@ -5,9 +5,7 @@ import { LIGHTS } from './data/lights.js'; import { PRIMITIVES } from './data/structure.js'; import { ASSETS } from './data/objects.js'; -export default async function build(api) { - const { scene, THREE, physics, loadLevel, loadGLTF } = api; - +export default async function build({ loadLevel }) { await loadLevel({ version: '2.0', worldKey: 'default', @@ -19,42 +17,6 @@ export default async function build(api) { sceneSettings: { sky: SKY }, }); - const ballPos = { x: 1.5, y: 0.4, z: 0 }; - const ballRadius = 0.25; - const ball = new THREE.Mesh( - new THREE.SphereGeometry(ballRadius, 24, 24), - new THREE.MeshPhysicalMaterial({ color: 0xe53935, roughness: 0.4, metalness: 0.1 }), - ); - ball.position.set(ballPos.x, ballPos.y, ballPos.z); - ball.castShadow = ball.receiveShadow = true; - scene.add(ball); - physics.staticCollider(ball, 'sphere'); - - const crate = new THREE.Mesh( - new THREE.BoxGeometry(0.6, 0.6, 0.6), - new THREE.MeshPhysicalMaterial({ color: 0x8b5a2b, roughness: 0.75 }), - ); - crate.position.set(3.2, 0.3, 1.5); - crate.castShadow = crate.receiveShadow = true; - scene.add(crate); - physics.staticCollider(crate, 'box'); - - try { - const gltf = await loadGLTF('/agent-model/dimsim_unitree_stub.glb'); - const prop = gltf.scene; - prop.position.set(-3, 0.3, 0); - prop.scale.set(0.4, 0.4, 0.4); - prop.traverse((o) => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } }); - scene.add(prop); - physics.staticCollider(prop, 'trimesh'); - } catch (e) { - console.warn('[scene] GLB load failed:', e); - } - - const accent = new THREE.PointLight(0xff6b6b, 6, 4); - accent.position.set(1.5, 1.8, 0); - scene.add(accent); - return { embodiment: null, spawnPoint: { x: 2, y: 0.5, z: 3 }, From 6ea4d7e62bdeb7bbb5d6b4a99b003098fe989e1e Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:32:35 -0700 Subject: [PATCH 28/43] misc/DimSim: strip HMR plumbing; make loadLevel/loadJson idempotent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HMR was structurally awkward — at every save the bridge's filesystem watcher (Deno.watchFs) broadcast {type:"reload"}, sceneEditor.ts dispatched to a __dimsimHmr handler in engine.js, that called sceneApi._revertToBaseline() and re-imported the scene. The agent was created *after* the baseline snapshot, so every reload erased it and a fresh build had no agent-creation code to re-add it. That's the "ball + robot disappear after editing" we just saw. Cleaner to drop the whole machinery and let users hard-refresh the browser to pick up scene edits: - cli/bridge/server.ts: removed the Deno.watchFs watcher + the {type:"reload"} broadcast loop. - src/engine.js: dropped sceneApi._captureBaseline() and the window.__dimsimHmr handler. - src/sceneEditor.ts: dropped the reload-message branch in the WS patcher. - src/sceneApi.ts: removed _captureBaseline / _revertToBaseline and their baseline state. - cli/bridge/physics.ts: refreshed a stale "hot-reloading" comment. While in there: - src/engine.js: added support for an optional `afterBuild(api)` scene export so scenes that use loadLevel can append imperative THREE.js code after the level is built. - src/sceneApi.ts: loadLevel + loadJson are now idempotent — calling either twice with the same data / URL is a no-op. importLevelFromJSON itself was already clean — its rebuildAssets() and rebuildAllPrimitives() pre-clear assetsGroup / primitivesGroup before re-adding, so no engine-side patch was needed (the (A) bullet turned out to be redundant — the actual culprit was _revertToBaseline, which is gone now). --- misc/DimSim/cli/bridge/physics.ts | 4 +- misc/DimSim/cli/bridge/server.ts | 38 ----------------- misc/DimSim/src/engine.js | 19 ++------- misc/DimSim/src/sceneApi.ts | 71 +++++++++++-------------------- misc/DimSim/src/sceneEditor.ts | 13 ------ 5 files changed, 30 insertions(+), 115 deletions(-) diff --git a/misc/DimSim/cli/bridge/physics.ts b/misc/DimSim/cli/bridge/physics.ts index d2bcf67f34..c3e5ec30fc 100644 --- a/misc/DimSim/cli/bridge/physics.ts +++ b/misc/DimSim/cli/bridge/physics.ts @@ -278,8 +278,8 @@ export class ServerPhysics { this.userColliders.delete(uuid); } - /** Drop every user-authored collider. Used when hot-reloading a scene - * from JSON — the new content's colliders get re-added afterwards. */ + /** Drop every user-authored collider — used when an explicit re-load + * of a level clears the existing scene content. */ clearUserColliders(): void { for (const uuid of [...this.userColliders.keys()]) { this.removeCollider(uuid); diff --git a/misc/DimSim/cli/bridge/server.ts b/misc/DimSim/cli/bridge/server.ts index 16b67a8a74..0cbf768074 100644 --- a/misc/DimSim/cli/bridge/server.ts +++ b/misc/DimSim/cli/bridge/server.ts @@ -179,44 +179,6 @@ export async function startBridgeServer(options: BridgeServerOptions) { channelMap.set(name, state); } - (async () => { - const candidates = [ - Deno.env.get("DIMSIM_SCENES_DIR"), - `${distDir}/scenes`, - `${distDir}/../scenes`, - ].filter((d): d is string => !!d && d.length > 0); - let watchDir: string | null = null; - for (const d of candidates) { - try { - await Deno.stat(`${d}/${activeSceneName}`); - watchDir = `${d}/${activeSceneName}`; - break; - } catch { /* try next */ } - } - if (!watchDir) return; - console.log(`[bridge] hot-reload watching ${watchDir}`); - let last = 0; - try { - for await (const event of Deno.watchFs(watchDir)) { - if (event.kind !== "modify" && event.kind !== "create") continue; - const now = performance.now(); - if (now - last < 250) continue; - last = now; - const msg = JSON.stringify({ type: "reload" }); - for (const ch of channelMap.values()) { - for (const client of ch.controlClients) { - if (client.readyState === WebSocket.OPEN) { - try { client.send(msg); } catch { /* ignore */ } - } - } - } - console.log(`[bridge] hot-reload`); - } - } catch (e) { - console.warn(`[bridge] watcher failed: ${e}`); - } - })(); - /** Resolve channel from WS query param. Falls back to default ("") if not found. */ function resolveChannel(channelParam: string | null): ChannelState { if (channelParam && channelMap.has(channelParam)) { diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index ef6ff87575..5ee01bca66 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7102,29 +7102,16 @@ if (dimosMode) { window.__dimsim = sceneApi; // 3. Dynamic-import the scene module + run its build() - sceneApi._captureBaseline(); const sceneMod = await import(/* @vite-ignore */ `/scenes/${sceneName}/index.js`); if (typeof sceneMod.default !== "function") { throw new Error(`Scene "${sceneName}" must export a default build function`); } const sceneCfg = (await sceneMod.default(sceneApi)) || {}; + if (typeof sceneMod.afterBuild === "function") { + await sceneMod.afterBuild(sceneApi); + } console.log(`[dimos] Scene loaded: ${sceneName}`); - // HMR: bridge broadcasts {type:"reload"} on scene-file edits. - // sceneEditor.ts dispatches to this handler, which re-runs build() - // without losing the engine/agent/bridge state. - window.__dimsimHmr = async () => { - console.log(`[dimos] HMR start for ${sceneName}`); - sceneApi._revertToBaseline(); - console.log(`[dimos] HMR reverted baseline, re-importing`); - const mod = await import(/* @vite-ignore */ - `/scenes/${sceneName}/index.js?t=${Date.now()}`); - console.log(`[dimos] HMR re-imported, calling build()`); - await mod.default(sceneApi); - console.log(`[dimos] hot-reloaded ${sceneName}`); - }; - console.log(`[dimos] __dimsimHmr installed`); - // 4. Spawn player + agent based on scene config spawnPlayerInsideScene(); // In dimos mode the agent is always driven by an external Python process, diff --git a/misc/DimSim/src/sceneApi.ts b/misc/DimSim/src/sceneApi.ts index 2b78decebc..9b4c465c5f 100644 --- a/misc/DimSim/src/sceneApi.ts +++ b/misc/DimSim/src/sceneApi.ts @@ -67,10 +67,7 @@ const _colliderMap = new Map(); const _dynamicBodies = new Map(); let _dynamicSyncRaf: number | null = null; -const _loadedJson = new Set(); -let _baselineChildren: Set | null = null; -let _baselineColliders: Set | null = null; -let _baselineDynamic: Set | null = null; +let _lastLoadedKey: string | null = null; // ── Initialization (called by engine.js once at boot) ──────────────────────── @@ -111,39 +108,6 @@ export function _setAgent(a: any): void { agent = a; } -/** Snapshot scene + collider state before the first build() runs. */ -export function _captureBaseline(): void { - _baselineChildren = new Set(scene.children); - _baselineColliders = new Set(_colliderMap.keys()); - _baselineDynamic = new Set(_dynamicBodies.keys()); -} - -/** Revert to baseline so build() can re-run cleanly (HMR). */ -export function _revertToBaseline(): void { - if (!_baselineChildren) return; - for (const obj of [...scene.children]) { - if (_baselineChildren.has(obj)) continue; - obj.traverse?.((c: any) => { - if (c.isMesh) { - c.geometry?.dispose?.(); - if (Array.isArray(c.material)) c.material.forEach((m: any) => m?.dispose?.()); - else c.material?.dispose?.(); - } - }); - scene.remove(obj); - } - for (const [uuid, coll] of [..._colliderMap.entries()]) { - if (_baselineColliders!.has(uuid)) continue; - try { rapierWorld?.removeCollider(coll, true); } catch { /* ignore */ } - _colliderMap.delete(uuid); - } - for (const [uuid, dyn] of [..._dynamicBodies.entries()]) { - if (_baselineDynamic!.has(uuid)) continue; - try { rapierWorld?.removeRigidBody(dyn.body); } catch { /* ignore */ } - _dynamicBodies.delete(uuid); - } -} - // ── Helpers ────────────────────────────────────────────────────────────────── export function loadGLTF(url: string): Promise { @@ -154,32 +118,47 @@ export function loadGLTF(url: string): Promise { } /** - * Load an existing JSON scene blob into the engine. Path is resolved relative - * to the scene module's folder, so `loadJson('./apt.json')` from - * `scenes/apartment/index.js` fetches `/scenes/apartment/apt.json`. + * Load an existing JSON scene blob into the engine. Idempotent — calling + * twice with the same URL is a no-op. */ export async function loadJson(path: string): Promise { if (!_importLevelFromJSON) throw new Error("scene-api not initialized"); const url = new URL(path, _sceneBaseUrl).toString(); - if (_loadedJson.has(url)) return; // HMR skip — already in scene + const key = `json:${url}`; + if (_lastLoadedKey === key) return; const resp = await fetch(url); if (!resp.ok) throw new Error(`loadJson(${path}): HTTP ${resp.status}`); const json = await resp.json(); _rewriteTexturePaths(json); await _importLevelFromJSON(json); - _loadedJson.add(url); + _lastLoadedKey = key; } /** - * Feed an in-memory level blob (apt-shape) directly to the engine. Use this - * when scene data lives in JS modules rather than a JSON file on disk. Any - * material with `texturePath: 'foo.jpg'` (relative to the scene folder) is - * rewritten to an absolute `textureDataUrl` before import. + * Feed an in-memory level blob directly to the engine. Idempotent — if the + * caller hands us the same object reference twice we skip the second import. + * Materials with `texturePath: 'foo.jpg'` are rewritten to absolute + * `textureDataUrl` against the scene base before import. */ export async function loadLevel(data: any): Promise { if (!_importLevelFromJSON) throw new Error("scene-api not initialized"); + const key = `level:${_identityOf(data)}`; + if (_lastLoadedKey === key) return; _rewriteTexturePaths(data); await _importLevelFromJSON(data); + _lastLoadedKey = key; +} + +const _identitySym = Symbol("dimsim.loadLevel.id"); +let _identityCounter = 0; +function _identityOf(o: any): string { + if (!o || typeof o !== "object") return String(o); + if (!o[_identitySym]) { + Object.defineProperty(o, _identitySym, { + value: String(++_identityCounter), enumerable: false, writable: false, + }); + } + return o[_identitySym]; } function _rewriteTexturePaths(node: any): void { diff --git a/misc/DimSim/src/sceneEditor.ts b/misc/DimSim/src/sceneEditor.ts index d30cafc7a1..558ca8db61 100644 --- a/misc/DimSim/src/sceneEditor.ts +++ b/misc/DimSim/src/sceneEditor.ts @@ -68,19 +68,6 @@ export class SceneEditor { this._handleCommand(cmd); return; } - if (cmd.type === "reload") { - const hmr = (globalThis as any).__dimsimHmr; - console.log(`[dimos] reload msg received, __dimsimHmr=${typeof hmr}`); - if (typeof hmr === "function") { - hmr().catch((e: any) => - console.error("[dimos] hot-reload failed:", e), - ); - } else { - console.log("[dimos] __dimsimHmr not set, falling back to location.reload"); - location.reload(); - } - return; - } } catch { /* not JSON or not for us */ } } // Pass through to existing handlers (EvalHarness, DimosBridge) From d6ac33c5ebf4b4d8c83fc79cd2c8ba2827356d95 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:40:05 -0700 Subject: [PATCH 29/43] misc/DimSim: scene-side setEmbodiment(config) API + docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lesh's #1691 "Robot definition API" — code a simple drone / holonomic / ground robot from inside a scene file. The bridge already handled embodimentConfig over WS (stored as chState.embodiment, calls ServerPhysics.reconfigure + ServerLidar. reconfigure live). Adds the missing JS-side surface: // sceneApi.ts export function setEmbodiment(config): void // scene file setEmbodiment({ embodimentType: 'drone', avatarUrl: '/agent-model/dimsim_unitree_stub.glb', radius: 0.3, halfHeight: 0.1, gravity: 0, maxSpeed: 3.0, turnRate: 2.0, maxAltitude: 8, }); setEmbodiment does two things at once: 1. Calls window.__dimosBridge._handleEmbodimentConfig(config) so the browser swaps the avatar GLB + un-hides the agent group. 2. Sends {type:'embodimentConfig', ...config} over the control WS so the bridge reconfigures server physics + lidar mount. cli/bridge/physics.ts:368 is the 6DoF flight branch; cli/bridge/lidar.ts reads the mount-height fields. No engine.js or bridge changes needed — just exposed what was already there. Wired into scenes/warehouse/index.js as a drone, documented in docs/scenes.md (new "Robot embodiment" section + a row in the api table). --- misc/DimSim/docs/scenes.md | 43 +++++++++++++++++++++++++++ misc/DimSim/scenes/warehouse/index.js | 22 +++++++++++++- misc/DimSim/src/sceneApi.ts | 26 ++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/misc/DimSim/docs/scenes.md b/misc/DimSim/docs/scenes.md index 88f61ae710..fe97b2565c 100644 --- a/misc/DimSim/docs/scenes.md +++ b/misc/DimSim/docs/scenes.md @@ -68,9 +68,52 @@ The whole `build()` re-runs on every save, so iteration is cheap. Try changing ` | `physics.staticCollider(mesh, shape)` | Make a mesh solid (agent can't walk through, lidar hits it). `shape`: `'box'` \| `'trimesh'` \| `'sphere'`. | | `physics.dynamicCollider(mesh, {mass, shape})` | Mesh becomes a rigid body — falls, can be pushed. Engine syncs `mesh.position` each frame. | | `setSky({...})` | Atmosphere. Keys: `topColor`, `horizonColor`, `bottomColor`, `brightness`, `softness`, `sunStrength`, `sunHeight`. | +| `setEmbodiment({...})` | Declare the agent — avatar GLB + capsule dimensions + physics mode + control params. See "Robot embodiment" below. | | `loadGLTF(url)` | Async GLB load — `await loadGLTF('./forklift.glb')` returns `{scene, animations, …}`. | | `agent`, `camera`, `renderer`, `RAPIER`, `rapierWorld` | Live engine refs if you need them. | +## Robot embodiment + +`setEmbodiment(config)` swaps the avatar mesh **and** reconfigures the bridge's server-side physics + lidar mount — so changing `embodimentType` from `'ground'` to `'drone'` instantly switches the cmd_vel → motion mapping from differential-drive (with gravity) to 6DoF flight (no gravity, altitude clamp). + +```js +// ground robot (default) +setEmbodiment({ + embodimentType: 'ground', + avatarUrl: '/agent-model/dimsim_unitree_stub.glb', + radius: 0.18, + halfHeight: 0.25, + maxSpeed: 1.5, + turnRate: 2.5, + gravity: -9.81, +}); + +// drone +setEmbodiment({ + embodimentType: 'drone', + avatarUrl: '/agent-model/dimsim_unitree_stub.glb', + radius: 0.3, + halfHeight: 0.1, + gravity: 0, + maxSpeed: 3.0, + turnRate: 2.0, + maxAltitude: 8, +}); +``` + +| Field | What | +|---|---| +| `embodimentType` | `'ground'` (differential-drive + gravity) or `'drone'` (6DoF). | +| `avatarUrl` | URL of the GLB to render. Bridge serves `/agent-model/*` from `public/`. | +| `radius`, `halfHeight` | Capsule collider dimensions. Also drive lidar mount height. | +| `maxSpeed` | Linear cmd_vel scale. | +| `turnRate` | Angular.z cmd_vel scale. | +| `gravity` | m/s². `0` for flight, `-9.81` for ground. | +| `maxAltitude` | Drone-only ceiling. | +| `lidarMountHeight`, `maxStepHeight`, `groundSnapDist`, `maxSlopeAngle`, `friction` | Optional fine-tuning. | + +Call it once at scene-build time (anywhere in `build()`), or call it again later to swap mid-scene. `scenes/warehouse/index.js` declares a drone as its first line — see it for a working example. + ## Return value `build()` can return `{ spawnPoint?, embodiment? }`: diff --git a/misc/DimSim/scenes/warehouse/index.js b/misc/DimSim/scenes/warehouse/index.js index 844ff2d7c4..dfb878b5c9 100644 --- a/misc/DimSim/scenes/warehouse/index.js +++ b/misc/DimSim/scenes/warehouse/index.js @@ -21,9 +21,20 @@ const SKY = { sunHeight: 0.6, }; -export default async function build({ scene, THREE, physics, setSky }) { +export default async function build({ scene, THREE, physics, setSky, setEmbodiment }) { setSky(SKY); + setEmbodiment({ + embodimentType: 'drone', + avatarUrl: '/agent-model/dimsim_unitree_stub.glb', + radius: 0.3, + halfHeight: 0.1, + gravity: 0, + maxSpeed: 3.0, + turnRate: 2.0, + maxAltitude: 8, + }); + // ── Floor ──────────────────────────────────────────────────────────────── const floor = new THREE.Mesh( new THREE.BoxGeometry(FLOOR.width, 0.2, FLOOR.depth), @@ -143,6 +154,15 @@ export default async function build({ scene, THREE, physics, setSky }) { dockDoor.position.set(FLOOR.width / 2 - 0.1, 1.8, 12); scene.add(dockDoor); + const ball = new THREE.Mesh( + new THREE.SphereGeometry(0.4, 32, 32), + new THREE.MeshPhysicalMaterial({ color: 0x2196f3, roughness: 0.35, metalness: 0.1 }), + ); + ball.position.set(0, 3.0, -10); + ball.castShadow = ball.receiveShadow = true; + scene.add(ball); + physics.dynamicCollider(ball, { shape: 'sphere', mass: 1.0, restitution: 0.6 }); + // ── Lights ─────────────────────────────────────────────────────────────── scene.add(new THREE.HemisphereLight(0xc8d4e0, 0x303030, 0.45)); diff --git a/misc/DimSim/src/sceneApi.ts b/misc/DimSim/src/sceneApi.ts index 9b4c465c5f..3ca80b6841 100644 --- a/misc/DimSim/src/sceneApi.ts +++ b/misc/DimSim/src/sceneApi.ts @@ -92,6 +92,32 @@ export function setSky(opts: Record): void { _setSky?.(opts); } +/** + * Declare the agent's embodiment — avatar mesh + body dimensions + physics + * mode + control parameters. Sent to the bridge so server-side physics + + * lidar mount reconfigure live, AND applied locally so the browser swaps + * the GLB and re-asserts visibility immediately. + * + * Typical configs: + * setEmbodiment({ embodimentType: 'ground', avatarUrl: '/agent-model/dimsim_unitree_stub.glb', + * radius: 0.18, halfHeight: 0.25, maxSpeed: 1.5, turnRate: 2.5 }); + * + * setEmbodiment({ embodimentType: 'drone', avatarUrl: '/agent-model/drone.glb', + * radius: 0.3, halfHeight: 0.1, gravity: 0, maxSpeed: 3.0 }); + * + * All fields are forwarded to the bridge's EmbodimentConfig (see + * cli/bridge/physics.ts). Falsy fields are passed through unchanged so + * partial reconfigures work — e.g. just bumping maxSpeed mid-scene. + */ +export function setEmbodiment(config: Record): void { + if (!_sendPhysics) throw new Error("scene-api not initialized"); + const w = window as any; + if (w.__dimosBridge?._handleEmbodimentConfig) { + w.__dimosBridge._handleEmbodimentConfig(config); + } + _sendPhysics({ type: "embodimentConfig", ...config }); +} + /** Engine.js calls this before loading a new scene, to refresh the base url. */ export function _setSceneBaseUrl(url: string): void { _sceneBaseUrl = url; From c3b8dd9a19382de1eae1e141c28d7986b8891943 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:43:12 -0700 Subject: [PATCH 30/43] fix(sceneApi): defer setEmbodiment() when bridge isn't up yet setEmbodiment called from scene build() was a silent no-op because engine.js sets window.__dimosBridge ~360 lines AFTER it runs the scene's build(); both the local _handleEmbodimentConfig hop and the WS send through _sendPhysics quietly resolved to undefined. Queue the config in sceneApi when the bridge isn't ready, and have engine.js call sceneApi._flushPendingEmbodiment() right after the window.__dimosBridge = bridge line. After that the bridge actually receives {type:'embodimentConfig', ...}, ServerPhysics.reconfigure fires, and the warehouse drone correctly hovers with gravity=0. --- misc/DimSim/src/engine.js | 1 + misc/DimSim/src/sceneApi.ts | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index 5ee01bca66..8bbefb9654 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7469,6 +7469,7 @@ if (dimosMode) { bridge.connect(); bridge.sceneReady = true; window.__dimosBridge = bridge; + sceneApi._flushPendingEmbodiment?.(); window.__dimosCapCam = _dimosCapCam; window.__dimosAgent = agent; diff --git a/misc/DimSim/src/sceneApi.ts b/misc/DimSim/src/sceneApi.ts index 3ca80b6841..bba039d413 100644 --- a/misc/DimSim/src/sceneApi.ts +++ b/misc/DimSim/src/sceneApi.ts @@ -109,13 +109,29 @@ export function setSky(opts: Record): void { * cli/bridge/physics.ts). Falsy fields are passed through unchanged so * partial reconfigures work — e.g. just bumping maxSpeed mid-scene. */ +let _pendingEmbodiment: Record | null = null; + export function setEmbodiment(config: Record): void { if (!_sendPhysics) throw new Error("scene-api not initialized"); const w = window as any; - if (w.__dimosBridge?._handleEmbodimentConfig) { - w.__dimosBridge._handleEmbodimentConfig(config); + if (w.__dimosBridge) { + if (w.__dimosBridge._handleEmbodimentConfig) { + w.__dimosBridge._handleEmbodimentConfig(config); + } + _sendPhysics({ type: "embodimentConfig", ...config }); + } else { + // Scene build() runs before engine.js wires up window.__dimosBridge. + // Queue and flush from engine.js once the bridge is constructed. + _pendingEmbodiment = config; } - _sendPhysics({ type: "embodimentConfig", ...config }); +} + +/** engine.js calls this right after `window.__dimosBridge = bridge`. */ +export function _flushPendingEmbodiment(): void { + if (!_pendingEmbodiment) return; + const cfg = _pendingEmbodiment; + _pendingEmbodiment = null; + setEmbodiment(cfg); } /** Engine.js calls this before loading a new scene, to refresh the base url. */ From aaba68570db8c2b40a4766bd8af506ec9b247dbc Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 18:49:11 -0700 Subject: [PATCH 31/43] warehouse: drop the experimental setEmbodiment(drone) line --- misc/DimSim/scenes/warehouse/index.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/misc/DimSim/scenes/warehouse/index.js b/misc/DimSim/scenes/warehouse/index.js index dfb878b5c9..2f111cc26a 100644 --- a/misc/DimSim/scenes/warehouse/index.js +++ b/misc/DimSim/scenes/warehouse/index.js @@ -21,20 +21,9 @@ const SKY = { sunHeight: 0.6, }; -export default async function build({ scene, THREE, physics, setSky, setEmbodiment }) { +export default async function build({ scene, THREE, physics, setSky }) { setSky(SKY); - setEmbodiment({ - embodimentType: 'drone', - avatarUrl: '/agent-model/dimsim_unitree_stub.glb', - radius: 0.3, - halfHeight: 0.1, - gravity: 0, - maxSpeed: 3.0, - turnRate: 2.0, - maxAltitude: 8, - }); - // ── Floor ──────────────────────────────────────────────────────────────── const floor = new THREE.Mesh( new THREE.BoxGeometry(FLOOR.width, 0.2, FLOOR.depth), From 78f92070ee1a61ece4a4fab8e4c42a0a95bd24ee Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 01:50:32 +0000 Subject: [PATCH 32/43] [autofix.ci] apply automated fixes --- dimos/simulation/dimsim/dimsim_process.py | 3 +- misc/DimSim/cli/agent.py | 17 +++++----- misc/DimSim/cli/test/dimos_integration.py | 41 +++++++++++++---------- misc/DimSim/scripts/extract_apt_to_js.py | 38 ++++++++++++++------- misc/DimSim/scripts/speed-test.py | 25 +++++++++----- 5 files changed, 75 insertions(+), 49 deletions(-) diff --git a/dimos/simulation/dimsim/dimsim_process.py b/dimos/simulation/dimsim/dimsim_process.py index beedfc4533..1630758145 100644 --- a/dimos/simulation/dimsim/dimsim_process.py +++ b/dimos/simulation/dimsim/dimsim_process.py @@ -148,8 +148,7 @@ def _resolve_dimsim_dir() -> Path: path = Path(local).expanduser().resolve() if not (path / "cli" / "cli.ts").exists(): raise RuntimeError( - f"DIMSIM_LOCAL={local} resolved to {path}, but " - f"{path}/cli/cli.ts does not exist" + f"DIMSIM_LOCAL={local} resolved to {path}, but {path}/cli/cli.ts does not exist" ) logger.info(f"Using local DimSim from {path}") return path diff --git a/misc/DimSim/cli/agent.py b/misc/DimSim/cli/agent.py index 3481eb138b..4f9a4d10a3 100644 --- a/misc/DimSim/cli/agent.py +++ b/misc/DimSim/cli/agent.py @@ -14,7 +14,6 @@ """ import argparse -import sys from dimos.core.blueprints import autoconnect from dimos.core.transport import JpegLcmTransport, LCMTransport @@ -35,13 +34,15 @@ } # Navigation stack: LiDAR → voxels → costmap → frontier explorer → path planner -nav = autoconnect( - voxel_mapper(voxel_size=0.1), - cost_mapper(algo="simple"), - replanning_a_star_planner(), - wavefront_frontier_explorer(), -).transports(_transports).global_config( - n_dask_workers=6, robot_model="dimsim" +nav = ( + autoconnect( + voxel_mapper(voxel_size=0.1), + cost_mapper(algo="simple"), + replanning_a_star_planner(), + wavefront_frontier_explorer(), + ) + .transports(_transports) + .global_config(n_dask_workers=6, robot_model="dimsim") ) diff --git a/misc/DimSim/cli/test/dimos_integration.py b/misc/DimSim/cli/test/dimos_integration.py index 51b07f06c6..1fd8a1abfe 100755 --- a/misc/DimSim/cli/test/dimos_integration.py +++ b/misc/DimSim/cli/test/dimos_integration.py @@ -27,12 +27,12 @@ --rate N cmd_vel publish rate in Hz (default: 10) """ -import sys -import time -import struct +import argparse import socket +import struct +import sys import threading -import argparse +import time # dimos message types for encoding cmd_vel from dimos.msgs.geometry_msgs.Twist import Twist @@ -46,6 +46,7 @@ # -- LCM packet codec (matches @dimos/msgs encodePacket / decodePacket) -------- + def encode_lcm_packet(channel: str, payload: bytes) -> bytes: """Encode an LCM binary packet (same format as @dimos/msgs encodePacket).""" global _seq @@ -64,16 +65,16 @@ def decode_lcm_packet(data: bytes) -> tuple[str, bytes]: raise ValueError(f"Bad magic: 0x{magic:08x}") null_pos = data.index(0, 8) channel = data[8:null_pos].decode("utf-8") - payload = data[null_pos + 1:] + payload = data[null_pos + 1 :] return channel, payload # -- Channel names (must match DimSim's dimosBridge.ts) ------------------------ CH_CMD_VEL = "/cmd_vel#geometry_msgs.Twist" -CH_ODOM = "/odom#geometry_msgs.PoseStamped" -CH_IMAGE = "/camera/image#sensor_msgs.Image" -CH_DEPTH = "/camera/depth#sensor_msgs.Image" -CH_LIDAR = "/lidar/points#sensor_msgs.PointCloud2" +CH_ODOM = "/odom#geometry_msgs.PoseStamped" +CH_IMAGE = "/camera/image#sensor_msgs.Image" +CH_DEPTH = "/camera/depth#sensor_msgs.Image" +CH_LIDAR = "/lidar/points#sensor_msgs.PointCloud2" def create_mcast_recv_socket() -> socket.socket: @@ -116,7 +117,7 @@ def main(): print(f"[integration] LCM multicast {MCAST_GRP}:{MCAST_PORT}") print(f"[integration] Publishing /cmd_vel at {args.rate} Hz") - print(f"[integration] Listening for sensor data on multicast") + print("[integration] Listening for sensor data on multicast") print(f"[integration] Timeout: {args.timeout}s\n") # -- Receive thread -------------------------------------------------------- @@ -124,7 +125,7 @@ def recv_loop(): while running: try: data, addr = recv_sock.recvfrom(65536) - except socket.timeout: + except TimeoutError: continue except OSError: break @@ -176,10 +177,12 @@ def recv_loop(): # Status check every 5s elapsed = time.time() - start_time if tick > 1 and (tick % (args.rate * 5) == 0): - print(f"\n[integration] STATUS ({elapsed:.0f}s): " - f"cmd_sent={tick} odom={received['odom']} " - f"rgb={received['image']} depth={received['depth']} " - f"lidar={received['lidar']}") + print( + f"\n[integration] STATUS ({elapsed:.0f}s): " + f"cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}" + ) if all(v > 0 for v in received.values()): success = True @@ -199,9 +202,11 @@ def recv_loop(): if not success: print(f"\n[integration] TIMEOUT after {args.timeout}s") - print(f"[integration] Final: cmd_sent={tick} odom={received['odom']} " - f"rgb={received['image']} depth={received['depth']} " - f"lidar={received['lidar']}") + print( + f"[integration] Final: cmd_sent={tick} odom={received['odom']} " + f"rgb={received['image']} depth={received['depth']} " + f"lidar={received['lidar']}" + ) except KeyboardInterrupt: print("\n[integration] Interrupted by user") diff --git a/misc/DimSim/scripts/extract_apt_to_js.py b/misc/DimSim/scripts/extract_apt_to_js.py index c87b522d36..55c0d161e5 100644 --- a/misc/DimSim/scripts/extract_apt_to_js.py +++ b/misc/DimSim/scripts/extract_apt_to_js.py @@ -25,9 +25,9 @@ import base64 import hashlib import json +from pathlib import Path import re import sys -from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCENE_DIR = ROOT / "scenes" / "apartment" @@ -36,12 +36,18 @@ def sniff_format(raw: bytes) -> str: - if raw[:4] == b"\x89PNG": return "png" - if raw[:2] == b"\xff\xd8": return "jpg" - if b"ftypavif" in raw[:32] or b"ftypmif1" in raw[:32]: return "avif" - if b"ftypheic" in raw[:32] or b"ftypheix" in raw[:32]: return "heic" - if raw[:6] in (b"GIF87a", b"GIF89a"): return "gif" - if raw[:4] == b"RIFF" and raw[8:12] == b"WEBP": return "webp" + if raw[:4] == b"\x89PNG": + return "png" + if raw[:2] == b"\xff\xd8": + return "jpg" + if b"ftypavif" in raw[:32] or b"ftypmif1" in raw[:32]: + return "avif" + if b"ftypheic" in raw[:32] or b"ftypheix" in raw[:32]: + return "heic" + if raw[:6] in (b"GIF87a", b"GIF89a"): + return "gif" + if raw[:4] == b"RIFF" and raw[8:12] == b"WEBP": + return "webp" return "bin" @@ -151,18 +157,26 @@ def main(apt_path: str | None = None) -> None: prims = doc.get("primitives", []) assets = doc.get("assets", []) - print(f"Emitting data/sky.js, data/tags.js, data/groups.js...") + print("Emitting data/sky.js, data/tags.js, data/groups.js...") emit_module("SKY", sky, DATA_DIR / "sky.js", note="Sky / atmosphere settings.") emit_module("TAGS", tags, DATA_DIR / "tags.js", note="World tag list (string filter tags).") emit_module("GROUPS", groups, DATA_DIR / "groups.js", note="Editor groupings.") print(f"Emitting data/lights.js ({len(lights)} lights)...") emit_module("LIGHTS", lights, DATA_DIR / "lights.js", note="Scene lights.") print(f"Emitting data/structure.js ({len(prims)} top-level primitives)...") - emit_module("PRIMITIVES", prims, DATA_DIR / "structure.js", - note="Static geometry (walls, floor, ceiling, fixtures).") + emit_module( + "PRIMITIVES", + prims, + DATA_DIR / "structure.js", + note="Static geometry (walls, floor, ceiling, fixtures).", + ) print(f"Emitting data/objects.js ({len(assets)} assets, incl. _deltaOnly)...") - emit_module("ASSETS", assets, DATA_DIR / "objects.js", - note="Placed assets with full state lists (interactive parity with apt.json).") + emit_module( + "ASSETS", + assets, + DATA_DIR / "objects.js", + note="Placed assets with full state lists (interactive parity with apt.json).", + ) print("Emitting index.js...") emit_index() diff --git a/misc/DimSim/scripts/speed-test.py b/misc/DimSim/scripts/speed-test.py index 731821ccfd..88162ae845 100644 --- a/misc/DimSim/scripts/speed-test.py +++ b/misc/DimSim/scripts/speed-test.py @@ -45,9 +45,11 @@ def _decode_lcm_packet(data: bytes): # Layout: hash(8) + 6 x float64 (linear.x,y,z, angular.x,y,z) -def encode_twist(linear_x=0.0, linear_y=0.0, linear_z=0.0, - angular_x=0.0, angular_y=0.0, angular_z=0.0) -> bytes: +def encode_twist( + linear_x=0.0, linear_y=0.0, linear_z=0.0, angular_x=0.0, angular_y=0.0, angular_z=0.0 +) -> bytes: from dimos.msgs.geometry_msgs import Twist, Vector3 + t = Twist( linear=Vector3(x=linear_x, y=linear_y, z=linear_z), angular=Vector3(x=angular_x, y=angular_y, z=angular_z), @@ -61,6 +63,7 @@ def encode_twist(linear_x=0.0, linear_y=0.0, linear_z=0.0, def decode_pose_stamped(data: bytes): """Decode geometry_msgs.PoseStamped using dimos LCM decoder.""" from dimos.msgs.geometry_msgs import PoseStamped + msg = PoseStamped.lcm_decode(data) p = msg.position q = msg.orientation @@ -96,14 +99,14 @@ def main(): send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) # ── Collect initial odom ── - print(f"Waiting for odom...") + print("Waiting for odom...") odom_channel = "/odom#geometry_msgs.PoseStamped" start_pose = None for _ in range(200): # up to 20s try: data, _ = sock.recvfrom(65535) - except socket.timeout: + except TimeoutError: continue ch, payload = _decode_lcm_packet(data) if ch == odom_channel: @@ -141,7 +144,7 @@ def send_cmd_vel(): while time.time() - t0 < duration: try: data, _ = sock.recvfrom(65535) - except socket.timeout: + except TimeoutError: continue ch, payload = _decode_lcm_packet(data) if ch == odom_channel: @@ -175,19 +178,23 @@ def send_cmd_vel(): print(f"End pose: ({ex:.3f}, {ey:.3f}, {ez:.3f})") print(f"Displacement: {dist:.3f}m") print(f"Expected: {expected:.3f}m") - print(f"Ratio: {ratio:.2f}x {'(OK)' if 0.85 < ratio < 1.15 else '(SLOW!)' if ratio < 0.85 else '(FAST!)'}") - print(f"Odom samples: {len(poses)} ({len(poses)/duration:.0f} Hz)") + print( + f"Ratio: {ratio:.2f}x {'(OK)' if 0.85 < ratio < 1.15 else '(SLOW!)' if ratio < 0.85 else '(FAST!)'}" + ) + print(f"Odom samples: {len(poses)} ({len(poses) / duration:.0f} Hz)") print(f"{'─' * 50}") # Show velocity over time - print(f"\nVelocity trace (sampled):") + print("\nVelocity trace (sampled):") prev_t, prev_p = 0, start_pose for i, (t, p) in enumerate(poses): if i % max(1, len(poses) // 10) != 0: continue dt = t - prev_t if dt > 0: - d = math.sqrt((p[0]-prev_p[0])**2 + (p[1]-prev_p[1])**2 + (p[2]-prev_p[2])**2) + d = math.sqrt( + (p[0] - prev_p[0]) ** 2 + (p[1] - prev_p[1]) ** 2 + (p[2] - prev_p[2]) ** 2 + ) v = d / dt print(f" t={t:5.2f}s v={v:.3f} m/s pos=({p[0]:.3f}, {p[1]:.3f}, {p[2]:.3f})") prev_t, prev_p = t, p From 355115dff1ad67bddc690fe6544f68b3125acb2f Mon Sep 17 00:00:00 2001 From: Viswajit <39920874+Viswa4599@users.noreply.github.com> Date: Tue, 19 May 2026 21:59:32 -0400 Subject: [PATCH 33/43] Update misc/DimSim/cli/bridge/server.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- misc/DimSim/cli/bridge/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/DimSim/cli/bridge/server.ts b/misc/DimSim/cli/bridge/server.ts index 0cbf768074..18a7830711 100644 --- a/misc/DimSim/cli/bridge/server.ts +++ b/misc/DimSim/cli/bridge/server.ts @@ -495,7 +495,7 @@ export async function startBridgeServer(options: BridgeServerOptions) { if (import.meta.main) { const distDir = new URL("../../dist", import.meta.url).pathname; - const scene = Deno.args.find((_a: string, i: number, arr: string[]) => arr[i - 1] === "--scene") || "apt"; + const scene = Deno.args.find((_a: string, i: number, arr: string[]) => arr[i - 1] === "--scene") || "apartment"; const port = parseInt(Deno.args.find((_a: string, i: number, arr: string[]) => arr[i - 1] === "--port") || "8090"); await startBridgeServer({ port, distDir, scene }); } From 767560b05b5a0eda24de63d912dd90cf8fd2a366 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Tue, 19 May 2026 20:43:07 -0700 Subject: [PATCH 34/43] scenes/dungeon: third-party GLB map demo + LFS rule for scenes/**/*.glb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Three.js-flavor "load a downloaded map" scene — extracted from the dungeon_warkarma.glb used in the webgl loader examples — to validate the dimos#1691 "load a third-party map" ask. The GLB (7.6 MB) ships via Git LFS. Adds a .gitattributes pattern that catches any future scene-local .glb / .gltf so we don't have to think about it per asset: misc/DimSim/scenes/**/*.glb filter=lfs diff=lfs merge=lfs -text misc/DimSim/scenes/**/*.gltf filter=lfs diff=lfs merge=lfs -text Scene itself is 40 lines — humanoid-sized embodiment + dark interior lighting + `await loadGLTF('./dungeon.glb')` + a trimesh collider. Drop in via `dimsim dev --scene dungeon` or with dimos as `--dimsim-scene=dungeon`. Verified: `git ls-files -s` shows the staged blob is the LFS pointer (132 bytes), `git lfs ls-files` confirms `cac0fc8c16 * misc/DimSim/scenes/dungeon/dungeon.glb`. --- .gitattributes | 2 + misc/DimSim/scenes/dungeon/dungeon.glb | 3 ++ misc/DimSim/scenes/dungeon/index.js | 54 ++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 misc/DimSim/scenes/dungeon/dungeon.glb create mode 100644 misc/DimSim/scenes/dungeon/index.js diff --git a/.gitattributes b/.gitattributes index b4ea3b5a3b..583a7a5848 100644 --- a/.gitattributes +++ b/.gitattributes @@ -21,3 +21,5 @@ docs/capabilities/memory/assets/.gitattributes -filter -diff -merge text # DimSim scene data and agent model misc/DimSim/public/sims/*.json filter=lfs diff=lfs merge=lfs -text misc/DimSim/public/agent-model/*.glb filter=lfs diff=lfs merge=lfs -text +misc/DimSim/scenes/**/*.glb filter=lfs diff=lfs merge=lfs -text +misc/DimSim/scenes/**/*.gltf filter=lfs diff=lfs merge=lfs -text diff --git a/misc/DimSim/scenes/dungeon/dungeon.glb b/misc/DimSim/scenes/dungeon/dungeon.glb new file mode 100644 index 0000000000..5e08800983 --- /dev/null +++ b/misc/DimSim/scenes/dungeon/dungeon.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cac0fc8c16d107e7ac4e69efde89c2cb6ef4bc66c34456a4dd0923218e5aafb1 +size 7990584 diff --git a/misc/DimSim/scenes/dungeon/index.js b/misc/DimSim/scenes/dungeon/index.js new file mode 100644 index 0000000000..da28af93f2 --- /dev/null +++ b/misc/DimSim/scenes/dungeon/index.js @@ -0,0 +1,54 @@ +const SKY = { + topColor: '#1a1a22', + horizonColor:'#33323a', + bottomColor: '#0c0c11', + brightness: 0.4, + softness: 1.2, + sunStrength: 0.15, + sunHeight: 0.7, +}; + +export default async function build({ scene, THREE, physics, setSky, setEmbodiment, loadGLTF }) { + setSky(SKY); + + setEmbodiment({ + embodimentType: 'ground', + radius: 0.25, + halfHeight: 0.6, + lidarMountHeight: 1.1, + gravity: -9.81, + maxSpeed: 1.4, + turnRate: 2.5, + }); + + scene.add(new THREE.AmbientLight(0xffeedd, 0.4)); + const torch = new THREE.DirectionalLight(0xffd28a, 1.4); + torch.position.set(20, 30, 10); + torch.castShadow = true; + torch.shadow.mapSize.set(2048, 2048); + torch.shadow.camera.left = -40; + torch.shadow.camera.right = 40; + torch.shadow.camera.top = 40; + torch.shadow.camera.bottom = -40; + scene.add(torch); + const fill = new THREE.HemisphereLight(0x4a6fa5, 0x1a1418, 0.35); + scene.add(fill); + + console.log('[dungeon] loading dungeon.glb (~7.6 MB)'); + const gltf = await loadGLTF('./dungeon.glb'); + const dungeon = gltf.scene; + dungeon.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + scene.add(dungeon); + physics.staticCollider(dungeon, 'trimesh'); + console.log('[dungeon] loaded'); + + return { + embodiment: null, + spawnPoint: { x: 0, y: 1.0, z: 0 }, + }; +} From 4beea81cb241bb19566ee06e05cc3cf6ed7eb557 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Wed, 20 May 2026 11:18:26 -0700 Subject: [PATCH 35/43] apartment: convert data/* to pure Three.js + structure.glb - inline sky/lights/tags into index.js - bake structure primitives to scenes/apartment/structure.glb - delete data/ and textures/ (orphaned) - add window.dimsim.exportStructure() + bridge /export-structure endpoint - add cli/export-structure.ts driver --- misc/DimSim/cli/bridge/server.ts | 40 + misc/DimSim/cli/export-apartment.ts | 127 + misc/DimSim/cli/export-structure.ts | 173 + misc/DimSim/scenes/apartment/data/groups.js | 175 - misc/DimSim/scenes/apartment/data/lights.js | 219 - misc/DimSim/scenes/apartment/data/objects.js | 69168 ---------------- misc/DimSim/scenes/apartment/data/sky.js | 11 - .../DimSim/scenes/apartment/data/structure.js | 5664 -- misc/DimSim/scenes/apartment/data/tags.js | 7 - misc/DimSim/scenes/apartment/index.js | 124 +- .../objects/_textures/029fc2889f15b8db.png | 3 + .../objects/_textures/04c871f0ab1c5a24.png | 3 + .../objects/_textures/0b7865c5ee29745f.png | 3 + .../objects/_textures/0d2c31fb2341e538.png | 3 + .../objects/_textures/0de92d4a4960538f.png | 3 + .../objects/_textures/149df7451fd5cd20.png | 3 + .../objects/_textures/18312adcf28b40cb.png | 3 + .../objects/_textures/1b1bd30153ae132d.png | 3 + .../objects/_textures/1dd8eb95b9656b26.png | 3 + .../objects/_textures/2243b7f1057033b2.png | 3 + .../objects/_textures/226283fd842aafd4.png | 3 + .../objects/_textures/31e13b5ed8a635c6.png | 3 + .../objects/_textures/39ae632dc93d53d5.png | 3 + .../objects/_textures/4a473b376851a332.png | 3 + .../objects/_textures/4cefc6fce1c0c9f9.png | 3 + .../objects/_textures/4fe42c4e88745307.png | 3 + .../objects/_textures/516aef161ed815e2.png | 3 + .../objects/_textures/593837db8223179b.png | 3 + .../objects/_textures/5a0cf43d6539c265.png | 3 + .../objects/_textures/5c01ca65028f6506.png | 3 + .../objects/_textures/6a6d187153f2d634.png | 3 + .../objects/_textures/748f081db9a0babd.png | 3 + .../objects/_textures/762ee1c4a1a1db1d.png | 3 + .../objects/_textures/77a18d10c12dcfb6.png | 3 + .../objects/_textures/7a60c10294c2d91c.png | 3 + .../objects/_textures/86f7b7c1dee6ac44.png | 3 + .../objects/_textures/9286d87f35fbff9d.png | 3 + .../objects/_textures/936d912a9d979920.png | 3 + .../objects/_textures/93d78e540cf2241a.png | 3 + .../objects/_textures/99621b614efba8ba.png | 3 + .../objects/_textures/9cbdb6a74476b4c7.png | 3 + .../objects/_textures/9edae70daab1b822.png | 3 + .../objects/_textures/a35e1f2bca4505ec.png | 3 + .../objects/_textures/a84b378c04f0f97f.png | 3 + .../objects/_textures/b53cdda7af4ed347.png | 3 + .../objects/_textures/b8800311c4074101.png | 3 + .../objects/_textures/c297f1edeca7326c.png | 3 + .../objects/_textures/ca8232ed9640b9f6.png | 3 + .../objects/_textures/d2d755c8d03e5e91.png | 3 + .../objects/_textures/d6b5c937576cb0d5.png | 3 + .../objects/_textures/e0be276a3c9143e0.png | 3 + .../objects/_textures/e4ca1adbdeae661e.png | 3 + .../objects/_textures/e7e2c3b1b49be3f4.png | 3 + .../objects/_textures/eaa5df17ce4675b1.png | 3 + .../objects/_textures/fda39aeccd530d8e.png | 3 + .../objects/arc-floor-lamp/state-default.glb | 3 + .../arc-floor-lamp/state-mlskixi4-xebf.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../objects/bedside-table/state-default.glb | 3 + .../bedside-table/state-mlsm2hfh-kpff.glb | 3 + .../state-default.glb | 3 + .../apartment/objects/books/state-default.glb | 3 + .../objects/bookshelf-decor/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../console-with-cabinets/state-default.glb | 3 + .../state-mlsncwfv-pq43.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../objects/dining-chair/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-mlsiig1y-6e45.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../hardcover-books-1/state-default.glb | 3 + .../objects/hardcover-books/state-default.glb | 3 + .../state-default.glb | 3 + .../kitchen-base-cabinet/state-default.glb | 3 + .../state-mlsr95pw-kpf4.glb | 3 + .../kitchen-wall-cabinet/state-default.glb | 3 + .../objects/laptop-open/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../objects/large-wardrobe/state-default.glb | 3 + .../large-wardrobe/state-mlsmklqi-mykf.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../scenes/apartment/objects/manifest.json | 3855 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../modern-office-work-desk/state-default.glb | 3 + .../state-default.glb | 3 + .../state-mlrpcv6c-bdvt.glb | 3 + .../neon-light-wall-art/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../professional-gas-range/state-default.glb | 3 + .../state-mlssjm1p-xotl.glb | 3 + .../state-mlsskac7-ywao.glb | 3 + .../state-mlssl1do-1yyl.glb | 3 + .../objects/queen-size-bed/state-default.glb | 3 + .../objects/refrigerator/state-default.glb | 3 + .../refrigerator/state-mlsqs928-jonu.glb | 3 + .../salt-and-pepper-shakers/state-default.glb | 3 + .../state-default.glb | 3 + .../apartment/objects/sink/state-default.glb | 3 + .../state-default.glb | 3 + .../small-potted-plant/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../television/state-mlr4bgf0-u7gv.glb | 3 + .../television/state-mlr4dvjl-fyw3.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../objects/vintage-poster/state-default.glb | 3 + .../objects/wall-clock/state-default.glb | 3 + .../objects/wall-mirror/state-default.glb | 3 + .../state-default.glb | 3 + .../state-default.glb | 3 + .../objects/wine-glass/state-default.glb | 3 + .../wine-glass/state-mlrogc5m-n4t3.glb | 3 + .../objects/wooden-bookcase/state-default.glb | 3 + .../state-default.glb | 3 + .../work-chair/state-mlrqdudw-h1ax.glb | 3 + .../woven-rattan-placemat/state-default.glb | 3 + .../state-default.glb | 3 + misc/DimSim/scenes/apartment/structure.glb | 3 + .../apartment/textures/048224345952.webp | Bin 136276 -> 0 bytes .../apartment/textures/08e5e2b877e1.jpg | 3 - .../apartment/textures/1c8ef077e400.jpg | 3 - .../apartment/textures/2518091b2c71.jpg | 3 - .../apartment/textures/2b666e22f0a6.jpg | 3 - .../apartment/textures/3885a68cb0cb.webp | Bin 13220 -> 0 bytes .../apartment/textures/4d5b048c925d.avif | Bin 65914 -> 0 bytes .../apartment/textures/84fdd8bbff5e.jpg | 3 - .../apartment/textures/9049fe942dc4.jpg | 3 - .../apartment/textures/a2ba694ad53e.jpg | 3 - .../apartment/textures/af07500a616c.webp | Bin 14942 -> 0 bytes .../apartment/textures/c83879b7f455.jpg | 3 - .../apartment/textures/d4679a510dcb.avif | Bin 1631263 -> 0 bytes .../apartment/textures/dc34e383b17a.jpg | 3 - .../apartment/textures/f29666a3db99.avif | Bin 124147 -> 0 bytes .../apartment/textures/faada43680d3.jpg | 3 - misc/DimSim/src/engine.js | 282 + 155 files changed, 4969 insertions(+), 75287 deletions(-) create mode 100644 misc/DimSim/cli/export-apartment.ts create mode 100644 misc/DimSim/cli/export-structure.ts delete mode 100644 misc/DimSim/scenes/apartment/data/groups.js delete mode 100644 misc/DimSim/scenes/apartment/data/lights.js delete mode 100644 misc/DimSim/scenes/apartment/data/objects.js delete mode 100644 misc/DimSim/scenes/apartment/data/sky.js delete mode 100644 misc/DimSim/scenes/apartment/data/structure.js delete mode 100644 misc/DimSim/scenes/apartment/data/tags.js create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png create mode 100644 misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png create mode 100644 misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb create mode 100644 misc/DimSim/scenes/apartment/objects/art-deco-gold-finished-bar-cart-with-two-glass-s/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/automatic-drip-coffee-maker-with-glass-carafe-i/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/bathroom-vanity-and-sink/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb create mode 100644 misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/books/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/built-in-stainless-steel-dishwasher/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/classic-outdoor-garden-bench/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb create mode 100644 misc/DimSim/scenes/apartment/objects/contemporary-white-ceramic-toilet-with-sleek-lin/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-mlsiig1y-6e45.glb create mode 100644 misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-mlsr95pw-kpf4.glb create mode 100644 misc/DimSim/scenes/apartment/objects/kitchen-wall-cabinet/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb create mode 100644 misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/manifest.json create mode 100644 misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/matte-ceramic-coffee-mug-with-steam-and-realisti/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb create mode 100644 misc/DimSim/scenes/apartment/objects/neon-light-wall-art/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/professional-gas-range/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssjm1p-xotl.glb create mode 100644 misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlsskac7-ywao.glb create mode 100644 misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssl1do-1yyl.glb create mode 100644 misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/refrigerator/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/refrigerator/state-mlsqs928-jonu.glb create mode 100644 misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/sink/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/stainless-steel-cutlery-set/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/television/state-mlr4bgf0-u7gv.glb create mode 100644 misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb create mode 100644 misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/two-slice-chrome-toaster-with-browning-control-d/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wall-clock/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wall-mirror/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wall-mounted-medicine-cabinet-with-a-large-mirro/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wall-mounted-stainless-steel-chimney-range-hood/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wine-glass/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wine-glass/state-mlrogc5m-n4t3.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb create mode 100644 misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb create mode 100644 misc/DimSim/scenes/apartment/structure.glb delete mode 100644 misc/DimSim/scenes/apartment/textures/048224345952.webp delete mode 100644 misc/DimSim/scenes/apartment/textures/08e5e2b877e1.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/1c8ef077e400.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/2518091b2c71.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/2b666e22f0a6.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/3885a68cb0cb.webp delete mode 100644 misc/DimSim/scenes/apartment/textures/4d5b048c925d.avif delete mode 100644 misc/DimSim/scenes/apartment/textures/84fdd8bbff5e.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/9049fe942dc4.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/a2ba694ad53e.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/af07500a616c.webp delete mode 100644 misc/DimSim/scenes/apartment/textures/c83879b7f455.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/d4679a510dcb.avif delete mode 100644 misc/DimSim/scenes/apartment/textures/dc34e383b17a.jpg delete mode 100644 misc/DimSim/scenes/apartment/textures/f29666a3db99.avif delete mode 100644 misc/DimSim/scenes/apartment/textures/faada43680d3.jpg diff --git a/misc/DimSim/cli/bridge/server.ts b/misc/DimSim/cli/bridge/server.ts index 18a7830711..fd7554f8d4 100644 --- a/misc/DimSim/cli/bridge/server.ts +++ b/misc/DimSim/cli/bridge/server.ts @@ -439,6 +439,46 @@ export async function startBridgeServer(options: BridgeServerOptions) { return response; } + // POST /export-asset?name=.glb → write a single GLB + // POST /export-manifest → write objects/manifest.json + // POST /export-structure → write apartment/structure.glb + // Used by the in-browser GLTFExporter pipeline to decompose a scene's + // assets back to disk (see src/engine.js → window.dimsim.exportApartmentAssets). + if (req.method === "POST" && (url.pathname === "/export-asset" || url.pathname === "/export-manifest" || url.pathname === "/export-structure")) { + const writeRoot = Deno.env.get("DIMSIM_SCENES_DIR") || `${distDir}/../scenes`; + const objectsDir = `${writeRoot}/apartment/objects`; + const apartmentDir = `${writeRoot}/apartment`; + if (url.pathname === "/export-structure") { + await Deno.mkdir(apartmentDir, { recursive: true }); + const body = new Uint8Array(await req.arrayBuffer()); + const target = `${apartmentDir}/structure.glb`; + await Deno.writeFile(target, body); + console.log(`[bridge] wrote structure.glb (${(body.byteLength/1024).toFixed(1)} KB)`); + return new Response("ok"); + } + await Deno.mkdir(objectsDir, { recursive: true }); + if (url.pathname === "/export-asset") { + const name = url.searchParams.get("name") || ""; + // Allow one level of nesting: /.glb + if (!/^[a-z0-9][a-z0-9-_]*(?:\/[a-z0-9][a-z0-9-_]*)?\.glb$/.test(name)) { + return new Response("bad name", { status: 400 }); + } + const body = new Uint8Array(await req.arrayBuffer()); + const target = `${objectsDir}/${name}`; + const slash = name.lastIndexOf("/"); + if (slash !== -1) { + await Deno.mkdir(`${objectsDir}/${name.slice(0, slash)}`, { recursive: true }); + } + await Deno.writeFile(target, body); + console.log(`[bridge] wrote ${name} (${(body.byteLength/1024).toFixed(1)} KB)`); + return new Response("ok"); + } + const text = await req.text(); + await Deno.writeTextFile(`${objectsDir}/manifest.json`, text); + console.log(`[bridge] wrote manifest.json`); + return new Response("ok"); + } + if (url.pathname === "/" || url.pathname === "/index.html") { try { let html = await Deno.readTextFile(`${distDir}/index.html`); diff --git a/misc/DimSim/cli/export-apartment.ts b/misc/DimSim/cli/export-apartment.ts new file mode 100644 index 0000000000..a99d5efafd --- /dev/null +++ b/misc/DimSim/cli/export-apartment.ts @@ -0,0 +1,127 @@ +/** + * Drives `window.dimsim.exportApartmentAssets()` against a freshly-booted + * apartment scene in headless Chromium. Writes GLBs + manifest.json into + * scenes/apartment/objects/ via the bridge's /export-asset endpoint. + * + * Run: + * # Full decompose: data-driven apartment → GLBs + manifest + * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-apartment.ts + * + * # Verify-only: just load the apartment and confirm assets instantiate + * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-apartment.ts --verify + * + * Requires system Chrome on macOS (bundled Chromium WebGL is broken): + * DIMSIM_CHROME_CHANNEL=chrome (set automatically below) + */ +import { launchHeadless } from "./headless/launcher.ts"; + +const PORT = 8099; +const DIST_DIR = new URL("../dist", import.meta.url).pathname; +const URL_BASE = `http://localhost:${PORT}`; +const VERIFY_ONLY = Deno.args.includes("--verify"); + +// Force system Chrome for WebGL on macOS — bundled Chromium can't render. +if (!Deno.env.get("DIMSIM_CHROME_CHANNEL")) { + Deno.env.set("DIMSIM_CHROME_CHANNEL", "chrome"); +} + +async function waitForUrl(url: string, timeoutMs = 15_000): Promise { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + const res = await fetch(url); + if (res.ok) return; + } catch { /* not up yet */ } + await new Promise((r) => setTimeout(r, 200)); + } + throw new Error(`bridge not ready within ${timeoutMs}ms`); +} + +async function main() { + // 1. Start bridge as subprocess + const bridgeCmd = new Deno.Command("deno", { + args: [ + "run", "--allow-all", "--unstable-net", + new URL("./bridge/server.ts", import.meta.url).pathname, + "--scene", "apartment", + "--port", String(PORT), + ], + stdout: "piped", stderr: "piped", + }); + const bridge = bridgeCmd.spawn(); + // Forward bridge logs so we can see /export-asset writes happen + (async () => { + const dec = new TextDecoder(); + for await (const c of bridge.stdout.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge] ${dec.decode(c)}`)); + })(); + (async () => { + const dec = new TextDecoder(); + for await (const c of bridge.stderr.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge!] ${dec.decode(c)}`)); + })(); + + console.log(`[driver] waiting for bridge on :${PORT}`); + await waitForUrl(`${URL_BASE}/`); + console.log(`[driver] bridge up`); + + // 2. Launch headless chrome + go to apartment + const inst = await launchHeadless({ url: `${URL_BASE}/`, render: "gpu", timeout: 60_000 }); + + try { + // 3. Wait until the apartment finishes instantiating all assets. + // The engine's dimos boot flips a "scene loaded" log after `loadLevel` + // completes; we detect that via the assets[] count growing past 80. + console.log(`[driver] waiting for apartment assets to instantiate`); + await inst.page.waitForFunction( + () => { + const w = window as unknown as { dimsim?: { exportApartmentAssets?: unknown } }; + // assets[] is closed over inside engine.js — exposed indirectly via the + // export helper, which only resolves once `assets` is populated. + if (!w.dimsim || typeof w.dimsim.exportApartmentAssets !== "function") return false; + // Probe: peek at how many asset groups exist in the scene + const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; + if (!scene) return false; + let n = 0; + scene.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); + return n >= 80; + }, + undefined, + { timeout: 60_000, polling: 500 }, + ); + + // Give the engine a bit more time to finish material+texture loads. + await new Promise((r) => setTimeout(r, 3000)); + + if (VERIFY_ONLY) { + const stats = await inst.page.evaluate(() => { + const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; + let n = 0; + scene?.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); + return { assetGroups: n }; + }); + console.log(`[driver] verify: ${stats.assetGroups} asset groups instantiated`); + } else { + console.log(`[driver] running exportApartmentAssets()`); + const result = await inst.page.evaluate(async () => { + const w = window as unknown as { dimsim: { exportApartmentAssets: () => Promise } }; + const m = await w.dimsim.exportApartmentAssets(); + return { count: m.length }; + }); + console.log(`[driver] export returned: ${result.count} asset entries`); + const manifestPath = new URL("../scenes/apartment/objects/manifest.json", import.meta.url).pathname; + const txt = await Deno.readTextFile(manifestPath); + const parsed = JSON.parse(txt); + console.log(`[driver] manifest.json: ${parsed.length} entries`); + const totalStates = parsed.reduce((acc: number, e: { states: unknown[] }) => acc + e.states.length, 0); + console.log(`[driver] total state-glbs: ${totalStates}`); + } + } finally { + await inst.close(); + try { bridge.kill("SIGTERM"); } catch { /* ignore */ } + await bridge.status; + } +} + +if (import.meta.main) { + await main(); + Deno.exit(0); +} diff --git a/misc/DimSim/cli/export-structure.ts b/misc/DimSim/cli/export-structure.ts new file mode 100644 index 0000000000..3eb5be3470 --- /dev/null +++ b/misc/DimSim/cli/export-structure.ts @@ -0,0 +1,173 @@ +/** + * One-shot conversion: bakes the apartment's static-structure primitives + * (data/structure.js) into scenes/apartment/structure.glb via the in-browser + * GLTFExporter. Mirrors cli/export-apartment.ts but boots a temporary + * data-driven index.js (so the primitives[] array is populated in the engine), + * runs window.dimsim.exportStructure(), then restores the GLB-driven index.js. + * + * Run: + * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-structure.ts + * + * Requires system Chrome on macOS (bundled Chromium WebGL is broken): + * DIMSIM_CHROME_CHANNEL=chrome (set automatically below) + */ +import { launchHeadless } from "./headless/launcher.ts"; + +const PORT = 8099; +const URL_BASE = `http://localhost:${PORT}`; + +const APARTMENT_INDEX = new URL("../scenes/apartment/index.js", import.meta.url).pathname; +const APARTMENT_INDEX_BACKUP = "/tmp/apartment.index.js.glb-driven"; +const STRUCTURE_GLB = new URL("../scenes/apartment/structure.glb", import.meta.url).pathname; + +// Minimal data-driven build() that populates engine.primitives[] from the +// existing data/structure.js. This is only used transiently during export — +// we restore the real (GLB-driven) index.js at the end. +const DATA_DRIVEN_INDEX = `// TEMP — used by cli/export-structure.ts to bake structure.glb. +// Restored to GLB-driven version automatically after export. +import { SKY } from './data/sky.js'; +import { TAGS } from './data/tags.js'; +import { GROUPS } from './data/groups.js'; +import { LIGHTS } from './data/lights.js'; +import { PRIMITIVES } from './data/structure.js'; +import { ASSETS } from './data/objects.js'; + +export default async function build({ loadLevel }) { + await loadLevel({ + version: '2.0', + worldKey: 'default', + tags: TAGS, + primitives: PRIMITIVES, + assets: ASSETS, + lights: LIGHTS, + groups: GROUPS, + sceneSettings: { sky: SKY }, + }); + + return { + embodiment: null, + spawnPoint: { x: 2, y: 0.5, z: 3 }, + }; +} +`; + +// Force system Chrome for WebGL on macOS — bundled Chromium can't render. +if (!Deno.env.get("DIMSIM_CHROME_CHANNEL")) { + Deno.env.set("DIMSIM_CHROME_CHANNEL", "chrome"); +} + +async function waitForUrl(url: string, timeoutMs = 15_000): Promise { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + const res = await fetch(url); + if (res.ok) return; + } catch { /* not up yet */ } + await new Promise((r) => setTimeout(r, 200)); + } + throw new Error(`bridge not ready within ${timeoutMs}ms`); +} + +async function fileExists(path: string): Promise { + try { + await Deno.stat(path); + return true; + } catch { + return false; + } +} + +async function main() { + // 1. Snapshot the current GLB-driven index.js and swap in a data-driven one. + const originalIndex = await Deno.readTextFile(APARTMENT_INDEX); + await Deno.writeTextFile(APARTMENT_INDEX_BACKUP, originalIndex); + await Deno.writeTextFile(APARTMENT_INDEX, DATA_DRIVEN_INDEX); + console.log(`[driver] swapped apartment/index.js to data-driven mode (backup at ${APARTMENT_INDEX_BACKUP})`); + + let bridge: Deno.ChildProcess | null = null; + try { + // 2. Start bridge as subprocess + const bridgeCmd = new Deno.Command("deno", { + args: [ + "run", "--allow-all", "--unstable-net", + new URL("./bridge/server.ts", import.meta.url).pathname, + "--scene", "apartment", + "--port", String(PORT), + ], + stdout: "piped", stderr: "piped", + }); + bridge = bridgeCmd.spawn(); + (async () => { + const dec = new TextDecoder(); + for await (const c of bridge!.stdout.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge] ${dec.decode(c)}`)); + })(); + (async () => { + const dec = new TextDecoder(); + for await (const c of bridge!.stderr.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge!] ${dec.decode(c)}`)); + })(); + + console.log(`[driver] waiting for bridge on :${PORT}`); + await waitForUrl(`${URL_BASE}/`); + console.log(`[driver] bridge up`); + + // 3. Launch headless chrome + go to apartment + const inst = await launchHeadless({ url: `${URL_BASE}/`, render: "gpu", timeout: 60_000 }); + + try { + // 4. Wait until the apartment finishes instantiating all assets — same + // gate as export-apartment.ts. Once asset groups are in the scene, + // the engine's primitives[] is also populated. + console.log(`[driver] waiting for apartment to finish loading`); + await inst.page.waitForFunction( + () => { + const w = window as unknown as { dimsim?: { exportStructure?: unknown } }; + if (!w.dimsim || typeof w.dimsim.exportStructure !== "function") return false; + const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; + if (!scene) return false; + let n = 0; + scene.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); + return n >= 80; + }, + undefined, + { timeout: 60_000, polling: 500 }, + ); + + // Give the engine a bit more time to finish material+texture loads. + await new Promise((r) => setTimeout(r, 3000)); + + console.log(`[driver] running exportStructure()`); + const result = await inst.page.evaluate(async () => { + const w = window as unknown as { dimsim: { exportStructure: () => Promise<{ bytes: number; count: number }> } }; + return await w.dimsim.exportStructure(); + }); + console.log(`[driver] export returned: ${result.count} primitives, ${(result.bytes/1024).toFixed(1)} KB`); + } finally { + await inst.close(); + } + } finally { + // 5. Always restore the GLB-driven index.js, even on error. + try { + const backup = await Deno.readTextFile(APARTMENT_INDEX_BACKUP); + await Deno.writeTextFile(APARTMENT_INDEX, backup); + console.log(`[driver] restored apartment/index.js from backup`); + } catch (e) { + console.error(`[driver] FAILED to restore index.js (backup still at ${APARTMENT_INDEX_BACKUP}):`, e); + } + if (bridge) { + try { bridge.kill("SIGTERM"); } catch { /* ignore */ } + await bridge.status; + } + } + + // 6. Verify the GLB landed on disk. + if (!(await fileExists(STRUCTURE_GLB))) { + throw new Error(`structure.glb was not written to ${STRUCTURE_GLB}`); + } + const stat = await Deno.stat(STRUCTURE_GLB); + console.log(`[driver] OK: ${STRUCTURE_GLB} (${(stat.size/1024).toFixed(1)} KB)`); +} + +if (import.meta.main) { + await main(); + Deno.exit(0); +} diff --git a/misc/DimSim/scenes/apartment/data/groups.js b/misc/DimSim/scenes/apartment/data/groups.js deleted file mode 100644 index 750bdd00e0..0000000000 --- a/misc/DimSim/scenes/apartment/data/groups.js +++ /dev/null @@ -1,175 +0,0 @@ -// Editor groupings. -export const GROUPS = [ - { - "id": "front-yard-group", - "name": "Front Yard", - "children": [ - "front-yard-floor", - "yard-wall-south", - "yard-wall-west", - "yard-wall-east" - ] - }, - { - "id": "exterior-walls-group", - "name": "Exterior Walls", - "children": [ - "wall-north", - "wall-east", - "wall-west", - "wall-south-seg-1", - "wall-south-seg-2", - "wall-south-seg-3", - "wall-south-header-entrance", - "wall-south-header-sliding" - ] - }, - { - "id": "main-divider-group", - "name": "Main Room Divider", - "children": [ - "wall-main-left", - "wall-main-right", - "wall-main-header" - ] - }, - { - "id": "kitchen-divider-group", - "name": "Kitchen Divider Wall", - "children": [ - "wall-kitchen-back", - "wall-kitchen-front", - "wall-kitchen-header" - ] - }, - { - "id": "bathroom-divider-group", - "name": "Bathroom Divider Wall", - "children": [ - "wall-bathroom-back", - "wall-bathroom-front", - "wall-bathroom-header" - ] - }, - { - "id": "ceiling-assembly", - "name": "Ceiling Assembly", - "children": [ - "ceiling-slab", - "downlight-cutout-1", - "downlight-cutout-2", - "downlight-cutout-3", - "downlight-cutout-4" - ] - }, - { - "id": "window-south-group", - "name": "Living Room Window", - "children": [ - "window-south-frame-top", - "window-south-frame-bottom", - "window-south-frame-left", - "window-south-frame-right", - "window-south-glass" - ] - }, - { - "id": "living-room-lighting", - "name": "Living Room Lighting", - "children": [ - "lr-light-trim-1", - "lr-light-trim-2", - "lr-light-trim-3", - "lr-light-trim-4" - ] - }, - { - "id": "kitchen-lighting", - "name": "Kitchen Lighting", - "children": [ - "kitchen-light-trim-1", - "kitchen-light-trim-2" - ] - }, - { - "id": "bedroom-lighting", - "name": "Bedroom Lighting", - "children": [ - "bedroom-light-trim-1", - "bedroom-light-trim-2" - ] - }, - { - "id": "bathroom-lighting", - "name": "Bathroom Lighting", - "children": [ - "bathroom-light-trim-1" - ] - }, - { - "id": "path-light-group-1", - "name": "Path Light 1", - "children": [ - "path-light-base-1" - ] - }, - { - "id": "path-light-group-2", - "name": "Path Light 2", - "children": [ - "path-light-base-2" - ] - }, - { - "id": "path-light-group-3", - "name": "Path Light 3", - "children": [ - "path-light-base-3" - ] - }, - { - "id": "living-room-furniture", - "name": "Living Room Furniture", - "children": [ - "sofa-base", - "sofa-back", - "coffee-table-top", - "tv-stand", - "tv-screen" - ] - }, - { - "id": "kitchen-furniture", - "name": "Kitchen Furniture", - "children": [ - "kitchen-counter-main", - "kitchen-fridge" - ] - }, - { - "id": "bedroom-furniture", - "name": "Bedroom Furniture", - "children": [ - "bed-frame", - "bed-mattress", - "wardrobe" - ] - }, - { - "id": "bathroom-furniture", - "name": "Bathroom Furniture", - "children": [ - "toilet-base", - "bathroom-sink" - ] - }, - { - "id": "yard-decor", - "name": "Yard Decor", - "children": [ - "yard-tree-trunk", - "yard-tree-leaves", - "yard-bench-seat" - ] - } -]; diff --git a/misc/DimSim/scenes/apartment/data/lights.js b/misc/DimSim/scenes/apartment/data/lights.js deleted file mode 100644 index a8c20488f9..0000000000 --- a/misc/DimSim/scenes/apartment/data/lights.js +++ /dev/null @@ -1,219 +0,0 @@ -// Scene lights. -export const LIGHTS = [ - { - "id": "lr-point-1", - "type": "point", - "color": "#FFFAF0", - "intensity": 2, - "position": { - "x": 2, - "y": 2.8, - "z": 2.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "lr-point-2", - "type": "point", - "color": "#FFFAF0", - "intensity": 0, - "position": { - "x": 4, - "y": 2.9, - "z": 1.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "kitchen-point-1", - "type": "point", - "color": "#F0FFFF", - "intensity": 2.5, - "position": { - "x": -4, - "y": 2.8, - "z": 2.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "kitchen-point-2", - "type": "point", - "color": "#F0FFFF", - "intensity": 2.5, - "position": { - "x": -4, - "y": 2.9, - "z": 3.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "bedroom-point-1", - "type": "point", - "color": "#FFE4B5", - "intensity": 2, - "position": { - "x": -2.5, - "y": 2.8, - "z": -2.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "bedroom-point-2", - "type": "point", - "color": "#FFE4B5", - "intensity": 1.5, - "position": { - "x": -2.5, - "y": 2.9, - "z": -3.5 - }, - "distance": 8, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "path-light-1", - "type": "point", - "color": "#FFD700", - "intensity": 1, - "distance": 3, - "position": { - "x": -2, - "y": 0.25, - "z": 5.8 - }, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "path-light-2", - "type": "point", - "color": "#FFD700", - "intensity": 1, - "distance": 3, - "position": { - "x": 0, - "y": 0.25, - "z": 5.8 - }, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "path-light-3", - "type": "point", - "color": "#FFD700", - "intensity": 1, - "distance": 3, - "position": { - "x": 2, - "y": 0.25, - "z": 5.8 - }, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "bathroom-point-1", - "type": "point", - "color": "#F0FFFF", - "intensity": 1.5, - "position": { - "x": 3.5, - "y": 2.8, - "z": -2.5 - }, - "distance": 6, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - }, - { - "id": "a6318996703218-19c731efc4c", - "type": "directional", - "name": "Directional Light", - "color": "#ffffff", - "intensity": 1, - "position": { - "x": 2.9245321131985382, - "y": 14.007232336425105, - "z": 16.10510431845022 - }, - "target": { - "x": 2.9990998365488326, - "y": 9.488242347246995, - "z": 13.966607385475479 - }, - "distance": 0, - "angle": 0.7853981633974483, - "penumbra": 0.1, - "castShadow": true, - "rotation": { - "x": 0.4418934675950337, - "y": 0.007056553181000234, - "z": 0.014914468913680328 - } - } -]; diff --git a/misc/DimSim/scenes/apartment/data/objects.js b/misc/DimSim/scenes/apartment/data/objects.js deleted file mode 100644 index 3ed3a550ea..0000000000 --- a/misc/DimSim/scenes/apartment/data/objects.js +++ /dev/null @@ -1,69168 +0,0 @@ -// Placed assets with full state lists (interactive parity with apt.json). -export const ASSETS = [ - { - "id": "5a9e88c39809f8-19c6d4d517a", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 2.9664440457140695, - "y": 0.07000015712080775, - "z": 7.905379735105829 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "5c16ece19475a8-19c6d618e4a", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 4.558749674574521, - "y": 0.11999986382360915, - "z": 0.9870423015154197 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1212101747817234, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "61a3e956e39b98-19c6d86a19a", - "currentStateId": "state-mlr4bgf0-u7gv", - "transform": { - "position": { - "x": 0.6042452590022527, - "y": 1.0071074813040573, - "z": 3.1192889023920864 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "c84b1ea2ecfb68-19c6d8878e9", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 4.4801470369107355, - "y": 0.12, - "z": 4.695561365728257 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1.221937082270584, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "5c5ea042e05108-19c6d89a8ac", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 4.016644385448475, - "y": 0.11999988633989321, - "z": 1.6286241215180994 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "adc490c50f0a8-19c6da2b519", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.7375816976226899, - "y": 0.022509114586181656, - "z": 3.75789081484642 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "d356cebb536148-19c6da75d6a", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -1.3838040108338954, - "y": 0.12, - "z": 4.156663940754135 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "99ce13dcfbbe18-19c6da85942", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -1.37, - "y": 0.12, - "z": 3.35533536603043 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "278ab9e187ab3-19c6da8d28c", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.16305586087266688, - "y": 0.12, - "z": 4.06 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "c990532fb773d-19c6da8d95f", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.23375282453076407, - "y": 0.12, - "z": 3.3862124174158508 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "aae132715c084-19c6dad8fa6", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -1.1607029122730868, - "y": 0.10922615278248099, - "z": 0.23218840228125132 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3371447453185796, - "y": 0.9711099989509588, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "388b66cc78309-19c6daeb09a", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 2.7619989756495933, - "y": 0.13979468925414756, - "z": 0.5176406893374147 - }, - "rotation": { - "x": 0, - "y": 0.38761669039714874, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "a84b5950ab807-19c6daf7371", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -4.629358937256292, - "y": 0.12, - "z": -2.41 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.1156019061766935, - "y": 1, - "z": 1.1452335563546119 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "8281460d6bd0b-19c6db0198b", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -5.48, - "y": 0.12, - "z": -1.1905193606535933 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "d80cf345e6378-19c6db073a0", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -5.4, - "y": 0.12, - "z": -3.6 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "c4f95189dba6f-19c6db0aeae", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.009154525843530548, - "y": 0.1199999293237277, - "z": -4.621224686852475 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "831fad8b31f09-19c6db15967", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -1.7695846290254031, - "y": 0.09863893167381843, - "z": -0.3748973485462799 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "a4fce47531926-19c6db26015", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 2.198037111894883, - "y": 0.11999991948345022, - "z": -4.637419694990235 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "652a29f1a9404-19c6db2a8e0", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 3.7231907584781903, - "y": 0.12, - "z": -0.8520858498752675 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1.38, - "y": 1, - "z": 1.13 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "0c5537fe8656a8-19c6db40545", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 4.335048920837586, - "y": 0.11999983452738137, - "z": -4.5605392691180375 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "6cd2ec9d3197c-19c6db4d775", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -5.6012120871504, - "y": 0.12, - "z": 3.988927985385395 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "9fffc0b3b27eb8-19c6db57014", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -5.594500658653977, - "y": 0.12, - "z": 2.22 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "c5bbc7e4cc65e-19c6db5d923", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -5.506344354346495, - "y": 0.12, - "z": 0.7143241693262312 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.2545273648897268, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "77218b8ad1cfa8-19c6db690ec", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 1.0982986318869128, - "y": 0.08174600897653289, - "z": 0.2121463946638324 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "a6eb0f9d43e7d-19c6f269603", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 0.069231121399475, - "y": 0.1200000340033187, - "z": -1.2902616534326783 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "1ffe8e03c465f8-19c6f381845", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 2.6183279921223432, - "y": 1.3350376656013772, - "z": 0.09500004260018356 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "b95523253e4f3-19c6f38b721", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 4.960000042945053, - "y": 1.2197129684645776, - "z": 1.6743614787175944 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "f81b5f2d83c0c8-19c6f41102b", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 0, - "y": 3.5700000000000003, - "z": -3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "0232d196c9042-19c6f429d8c", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 0, - "y": 3.5700000000000003, - "z": -3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "30079295b13dd8-19c6f42aa27", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 0.7437660712760077, - "y": 3.5700000000000003, - "z": -2.973000030517578 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "1572384c4a97c8-19c6f4436b2", - "currentStateId": "state-default", - "transform": { - "position": { - "x": 0, - "y": 3.5700000000000003, - "z": -3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "66b3fe34d5d798-19c6f58205c", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.4791040475328721, - "y": 0.5305782814740521, - "z": 3.4845390260736218 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "43c84f57996928-19c6f5d5fb3", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.7825572266167303, - "y": 0.13979541283713964, - "z": 4.383668680709073 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "d0692d80895cf8-19c6f66a1f8", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -1.6932116152156742, - "y": 0.5413564259043413, - "z": 3.7098366315591425 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": true, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "04e0b58fad3818-19c6f698453", - "currentStateId": "state-default", - "transform": { - "position": { - "x": -0.89, - "y": 0.5488175346466267, - "z": 2.97 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "ffdb71b93df8f-19c6fba0314", - "currentStateId": "state-mlrqdudw-h1ax", - "transform": { - "position": { - "x": -4.966385825145529, - "y": 0.8739173637259603, - "z": -4.419859229869855 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "castShadow": false, - "receiveShadow": false, - "blobShadow": null, - "_deltaOnly": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9 - }, - { - "id": "59a525468c75d8-19c73105016", - "title": "Modern L-shaped sectional", - "notes": "Modern L-shaped sectional sofa with plush cushions and fabric upholstery\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "sofa-base-main", - "type": "box", - "name": "Main Base", - "dimensions": { - "width": 2.4, - "height": 0.2, - "depth": 0.9, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.15, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-base-return", - "type": "box", - "name": "Return Base", - "dimensions": { - "width": 0.9, - "height": 0.2, - "depth": 0.9, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0.7582945087980211, - "y": 0.15, - "z": 0.9000000000000001 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-back-main", - "type": "box", - "name": "Main Backrest", - "dimensions": { - "width": 2.4, - "height": 0.5, - "depth": 0.15, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.5, - "z": -0.375 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.4, - "repeatY": 3.3, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-back-return", - "type": "box", - "name": "Return Backrest", - "dimensions": { - "width": 0.15, - "height": 0.5, - "depth": 1.65, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 1.125, - "y": 0.5, - "z": 0.525 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-arm-left", - "type": "box", - "name": "Left Armrest", - "dimensions": { - "width": 0.15, - "height": 0.4, - "depth": 0.9, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": -1.125, - "y": 0.45000000000000007, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1.4, - "repeatY": 1.4, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-cushion-4", - "type": "box", - "name": "Seat Cushion 4", - "dimensions": { - "width": 0.75, - "height": 0.2, - "depth": 0.75, - "edgeRadius": 0.05 - }, - "transform": { - "position": { - "x": 0.6833058524757464, - "y": 0.35, - "z": 0.5128482750443774 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 2.1861128216835417 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-1", - "type": "cylinder", - "name": "Leg 1", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": -1.1, - "y": 0.024999999999999994, - "z": -0.39999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-2", - "type": "cylinder", - "name": "Leg 2", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": 1.1, - "y": 0.024999999999999994, - "z": -0.39999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-3", - "type": "cylinder", - "name": "Leg 3", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": -1.1, - "y": 0.024999999999999994, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-4", - "type": "cylinder", - "name": "Leg 4", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": 0.4, - "y": 0.024999999999999994, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-5", - "type": "cylinder", - "name": "Leg 5", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": 0.4, - "y": 0.024999999999999994, - "z": 1.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "sofa-leg-6", - "type": "cylinder", - "name": "Leg 6", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.015, - "height": 0.05 - }, - "transform": { - "position": { - "x": 1.1, - "y": 0.024999999999999994, - "z": 1.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0, - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "ead12c019651a8-19c72aabac1", - "type": "box", - "name": "Seat Cushion 4 copy", - "dimensions": { - "width": 0.75, - "height": 0.2, - "depth": 0.75, - "edgeRadius": 0.05 - }, - "transform": { - "position": { - "x": -0.3685056726768088, - "y": 0.35, - "z": 0.08137051300131176 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1.8163399593476082 - } - }, - "material": { - "color": "#615851", - "softness": 0.92, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "cc784ee0045a-19c72ca3c95", - "name": "Group", - "children": [ - "sofa-base-main", - "sofa-base-return", - "sofa-back-main", - "sofa-back-return", - "sofa-arm-left", - "sofa-cushion-4", - "ead12c019651a8-19c72aabac1" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 4.3815894523994166, - "y": 0.49017637093479277, - "z": 1.0556627709770972 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1238777028003708, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsjiggj-x005", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.004147224596688148, - "y": 0.3749999998137355, - "z": 0.44999999254941947 - } - }, - { - "id": "95e67f414e06f8-19c73110d54", - "title": "Wooden coffee table with glass top", - "notes": "Low-profile wooden coffee table with a smooth finish and metal legs\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "coffee-table-top", - "type": "box", - "name": "Table Top", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.43, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#464543", - "softness": 0.02, - "metalness": 0, - "roughness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "textureDataUrl": null, - "opacity": 0.41, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "13331d028910b-19c72b00605", - "type": "box", - "name": "Table Top copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.0021206463573126833, - "y": 0.43, - "z": 0.32358433887608723 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08995597209615729 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "c8fe273aac16f8-19c72b0795b", - "type": "box", - "name": "Table Top copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.001980104126764859, - "y": 0.43, - "z": -0.3206162506556107 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08995597209615729 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "ec0076266801f8-19c72b0a78f", - "type": "box", - "name": "Table Top copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.6203396059313766, - "y": 0.43, - "z": 0.0008340409638994128 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.04979141799701496, - "y": 1, - "z": 1.1729480639560692 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "90cd3130b5cb98-19c72b23e8a", - "type": "box", - "name": "Table Top copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.6235017441064126, - "y": 0.43, - "z": 0.0008340409638994128 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.04979141799701496, - "y": 1, - "z": 1.1729480639560692 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "7e80be91700718-19c72b2b87c", - "type": "box", - "name": "Table Top copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.6210467679183226, - "y": 0.23609179846964373, - "z": -0.3246588873709795 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05, - "y": 1.3242147516910248, - "z": 0.7063378154563944 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "71ec4acf05512-19c72b42534", - "type": "box", - "name": "Table Top copy copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.6205853521989105, - "y": 0.23609179846964373, - "z": 0.3286532299120229 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05, - "y": 1.3242147516910248, - "z": 0.7063378154563944 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "e36b247e95b9c8-19c72b4b5b6", - "type": "box", - "name": "Table Top copy copy copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.6223341343191969, - "y": 0.23609179846964373, - "z": 0.3269170421786899 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05, - "y": 1.3242147516910248, - "z": 0.7063378154563944 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "035a7dc0c14778-19c72b5358f", - "type": "box", - "name": "Table Top copy copy copy copy copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.6259950459126888, - "y": 0.23609179846964373, - "z": -0.3234614013184688 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05, - "y": 1.3242147516910248, - "z": 0.7063378154563944 - } - }, - "material": { - "color": "#A07855", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "3d4ae7c4a3d38-19c72b594d3", - "type": "box", - "name": "Table Top copy", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.0066506960547139116, - "y": 0.17000168414944178, - "z": 0.004325935390667723 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.001753546599978, - "y": 1, - "z": 1.126721586410172 - } - }, - "material": { - "color": "#A07855", - "softness": 0.88, - "metalness": 0, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.5, - "textureHardness": 0.2, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "657f807a851ed-19c72b7dee0", - "name": "Table Top", - "children": [ - "coffee-table-top", - "13331d028910b-19c72b00605", - "c8fe273aac16f8-19c72b0795b", - "ec0076266801f8-19c72b0a78f", - "90cd3130b5cb98-19c72b23e8a", - "7e80be91700718-19c72b2b87c", - "71ec4acf05512-19c72b42534", - "e36b247e95b9c8-19c72b4b5b6", - "035a7dc0c14778-19c72b5358f", - "3d4ae7c4a3d38-19c72b594d3" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 3.953765809257721, - "y": 0.315281337994577, - "z": 1.7067945710626398 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 1.25, - "bumpDamping": 0.78, - "libraryAssetId": "stg-mlsjj0g8-pht2", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.0024741389971831285, - "y": 0.23709522248274384, - "z": 0.001997171270521708 - } - }, - { - "id": "2f1b51866fa368-19c731443ef", - "title": "Console with cabinets", - "notes": "Sleek wooden TV stand with cabinets and tapered legs\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "scene": { - "tags": [], - "primitives": [ - { - "id": "tv-stand-top", - "type": "box", - "name": "Top Surface", - "dimensions": { - "width": 1.6, - "height": 0.03, - "depth": 0.4, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4678466629837381, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "dark walnut wood grain", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-bottom", - "type": "box", - "name": "Bottom Base", - "dimensions": { - "width": 1.6, - "height": 0.03, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.165, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-side-l", - "type": "box", - "name": "Left Side", - "dimensions": { - "width": 0.03, - "height": 0.29, - "depth": 0.4 - }, - "transform": { - "position": { - "x": -0.785, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-side-r", - "type": "box", - "name": "Right Side", - "dimensions": { - "width": 0.03, - "height": 0.29, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0.785, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-div-l", - "type": "box", - "name": "Left Divider", - "dimensions": { - "width": 0.02, - "height": 0.29, - "depth": 0.38 - }, - "transform": { - "position": { - "x": -0.26, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-div-r", - "type": "box", - "name": "Right Divider", - "dimensions": { - "width": 0.02, - "height": 0.29, - "depth": 0.38 - }, - "transform": { - "position": { - "x": 0.26, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-door-l", - "type": "box", - "name": "Left Cabinet Door", - "dimensions": { - "width": 0.5, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.52, - "y": 0.325, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "vertical wood slats", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-door-r", - "type": "box", - "name": "Right Cabinet Door", - "dimensions": { - "width": 0.5, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.52, - "y": 0.325, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "vertical wood slats", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-shelf-mid", - "type": "box", - "name": "Middle Shelf", - "dimensions": { - "width": 0.5, - "height": 0.02, - "depth": 0.38 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.7, - "y": 0.07500000000000001, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.7, - "y": 0.07500000000000001, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-leg-bl", - "type": "cylinder", - "name": "Back Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.7, - "y": 0.07500000000000001, - "z": -0.14999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-leg-br", - "type": "cylinder", - "name": "Back Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.7, - "y": 0.07500000000000001, - "z": -0.14999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.08 - }, - "transform": { - "position": { - "x": -0.35, - "y": 0.325, - "z": 0.21500000000000002 - }, - "rotation": { - "x": 1.5708000000000009, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "tv-stand-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0.35, - "y": 0.325, - "z": 0.21500000000000002 - }, - "rotation": { - "x": 1.5708000000000009, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "sleek-tv-stand", - "name": "Sleek Wooden TV Stand", - "children": [ - "tv-stand-top", - "tv-stand-bottom", - "tv-stand-side-l", - "tv-stand-side-r", - "tv-stand-div-l", - "tv-stand-div-r", - "tv-stand-door-l", - "tv-stand-door-r", - "tv-stand-shelf-mid", - "tv-stand-leg-fl", - "tv-stand-leg-fr", - "tv-stand-leg-bl", - "tv-stand-leg-br", - "tv-stand-handle-l", - "tv-stand-handle-r" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsncwfv-pq43", - "label": "to Open", - "to": "state-mlsncwfv-pq43" - } - ] - }, - { - "id": "state-mlsncwfv-pq43", - "name": "Open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "tv-stand-top", - "type": "box", - "name": "Top Surface", - "dimensions": { - "width": 1.6, - "height": 0.03, - "depth": 0.4, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4678466629837381, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "dark walnut wood grain", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-bottom", - "type": "box", - "name": "Bottom Base", - "dimensions": { - "width": 1.6, - "height": 0.03, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.165, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-side-l", - "type": "box", - "name": "Left Side", - "dimensions": { - "width": 0.03, - "height": 0.29, - "depth": 0.4 - }, - "transform": { - "position": { - "x": -0.785, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-side-r", - "type": "box", - "name": "Right Side", - "dimensions": { - "width": 0.03, - "height": 0.29, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0.785, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-div-l", - "type": "box", - "name": "Left Divider", - "dimensions": { - "width": 0.02, - "height": 0.29, - "depth": 0.38 - }, - "transform": { - "position": { - "x": -0.26, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-div-r", - "type": "box", - "name": "Right Divider", - "dimensions": { - "width": 0.02, - "height": 0.29, - "depth": 0.38 - }, - "transform": { - "position": { - "x": 0.26, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-door-l", - "type": "box", - "name": "Left Cabinet Door", - "dimensions": { - "width": 0.5, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.7691567743146803, - "y": 0.32031691482527325, - "z": 0.44778864193575046 - }, - "rotation": { - "x": 0, - "y": -1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "vertical wood slats", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-door-r", - "type": "box", - "name": "Right Cabinet Door", - "dimensions": { - "width": 0.5, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.52, - "y": 0.325, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "vertical wood slats", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-shelf-mid", - "type": "box", - "name": "Middle Shelf", - "dimensions": { - "width": 0.5, - "height": 0.02, - "depth": 0.38 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.7, - "y": 0.07500000000000001, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.7, - "y": 0.07500000000000001, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-leg-bl", - "type": "cylinder", - "name": "Back Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.7, - "y": 0.07500000000000001, - "z": -0.14999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-leg-br", - "type": "cylinder", - "name": "Back Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.012, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.7, - "y": 0.07500000000000001, - "z": -0.14999999999999997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.3, - "metalness": 0, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.08 - }, - "transform": { - "position": { - "x": -0.7841567743146802, - "y": 0.32031691482527325, - "z": 0.6177886419357506 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 3.6732051045345495e-06, - "z": 1.5707963267948963 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tv-stand-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0.35, - "y": 0.325, - "z": 0.21500000000000002 - }, - "rotation": { - "x": 1.5708000000000009, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "01829b3e5adb5-19c73058401", - "type": "box", - "name": "Left Cabinet Door copy", - "dimensions": { - "width": 0.5, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.520277715775591, - "y": 0.32, - "z": -0.1833694132788502 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.5, - "metalness": 0, - "generateTexture": "vertical wood slats", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "82155f5482e5b-19c73063ebd", - "name": "Sleek Wooden TV Stand", - "children": [ - "tv-stand-top", - "tv-stand-bottom", - "tv-stand-side-l", - "tv-stand-side-r", - "tv-stand-div-l", - "tv-stand-div-r", - "tv-stand-door-l", - "tv-stand-door-r", - "tv-stand-shelf-mid", - "tv-stand-leg-fl", - "tv-stand-leg-fr", - "tv-stand-leg-bl", - "tv-stand-leg-br", - "tv-stand-handle-l", - "tv-stand-handle-r", - "01829b3e5adb5-19c73058401" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsncwfv-pq43-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsncwfv-pq43", - "label": "to Open", - "from": "state-default", - "to": "state-mlsncwfv-pq43" - }, - { - "id": "cycle-state-mlsncwfv-pq43-to-state-default", - "label": "to closed", - "from": "state-mlsncwfv-pq43", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": 4.516290373580539, - "y": 0.3523583672864196, - "z": 4.64489994581474 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsjlem3-s8q3", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.24142332983411488, - "z": 0.027500016428739227 - } - }, - { - "id": "c7899f757219b-19c731bc6ff", - "title": "Television", - "notes": "Saved asset", - "states": [ - { - "id": "state-mlr4bgf0-u7gv", - "name": "OFF", - "interactions": [ - { - "id": "cycle-state-mlr4bgf0-u7gv-to-state-mlr4dvjl-fyw3", - "label": "to ON", - "to": "state-mlr4dvjl-fyw3" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "3ebefa9884662-19c6d7e9942", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.850017786026001, - "y": 0.5608133301050605, - "z": 1.7661258300199816 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7335441149829043, - "y": 1.2399367342921863, - "z": 0.02046274841912012 - } - }, - "material": { - "color": "#000000", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "37e461ae5bb63-19c6d7fff96", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 4.756352459123096, - "y": 0.5580638877725551, - "z": 1.7673126819631615 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.06135519699427412, - "y": 1.3293681607336658, - "z": 0.03311795901506161 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "c31e6940f9e9d8-19c6d81bbc6", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 2.969096695797887, - "y": 0.5580638877725551, - "z": 1.7673126819631615 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.06135519699427412, - "y": 1.3293681607336658, - "z": 0.03311795901506161 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "355d2abc8f3ea8-19c6d82077d", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.859197711234289, - "y": 1.1881227527639944, - "z": 1.77 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5707963267948963 - }, - "scale": { - "x": 0.06, - "y": 1.7343534948578136, - "z": 0.03 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "4e7d2da2bb8508-19c6d82d96f", - "type": "box", - "name": "Box copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.857736677861182, - "y": -0.07968822684260812, - "z": 1.77 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5707963267948963 - }, - "scale": { - "x": 0.06, - "y": 1.7343534948578136, - "z": 0.03 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "9e829bafde0988-19c6d834932", - "name": "Box", - "children": [ - "3ebefa9884662-19c6d7e9942", - "37e461ae5bb63-19c6d7fff96", - "c31e6940f9e9d8-19c6d81bbc6", - "355d2abc8f3ea8-19c6d82077d", - "4e7d2da2bb8508-19c6d82d96f" - ], - "pickable": false - } - ] - } - }, - { - "id": "state-mlr4dvjl-fyw3", - "name": "ON", - "interactions": [ - { - "id": "cycle-state-mlr4dvjl-fyw3-to-state-mlr4bgf0-u7gv", - "label": "to OFF", - "to": "state-mlr4bgf0-u7gv" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "3ebefa9884662-19c6d7e9942", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.850017786026001, - "y": 0.5608133301050605, - "z": 1.7661258300199816 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7335441149829043, - "y": 1.2399367342921863, - "z": 0.02046274841912012 - } - }, - "material": { - "color": "#c4c4c4", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2518091b2c71.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "37e461ae5bb63-19c6d7fff96", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 4.756352459123096, - "y": 0.5580638877725551, - "z": 1.7673126819631615 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.06135519699427412, - "y": 1.3293681607336658, - "z": 0.03311795901506161 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "c31e6940f9e9d8-19c6d81bbc6", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 2.969096695797887, - "y": 0.5580638877725551, - "z": 1.7673126819631615 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.06135519699427412, - "y": 1.3293681607336658, - "z": 0.03311795901506161 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "355d2abc8f3ea8-19c6d82077d", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.859197711234289, - "y": 1.1881227527639944, - "z": 1.77 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5707963267948963 - }, - "scale": { - "x": 0.06, - "y": 1.7343534948578136, - "z": 0.03 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "4e7d2da2bb8508-19c6d82d96f", - "type": "box", - "name": "Box copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 3.857736677861182, - "y": -0.07968822684260812, - "z": 1.77 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5707963267948963 - }, - "scale": { - "x": 0.06, - "y": 1.7343534948578136, - "z": 0.03 - } - }, - "material": { - "color": "#000000", - "roughness": 0, - "softness": 0, - "hardness": 0.47, - "fluffiness": 0, - "metalness": 1, - "specularIntensity": 0, - "specularColor": "#000000", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - } - ], - "lights": [], - "groups": [ - { - "id": "9e829bafde0988-19c6d834932", - "name": "Box", - "children": [ - "3ebefa9884662-19c6d7e9942", - "37e461ae5bb63-19c6d7fff96", - "c31e6940f9e9d8-19c6d81bbc6", - "355d2abc8f3ea8-19c6d82077d", - "4e7d2da2bb8508-19c6d82d96f" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-mlr4bgf0-u7gv", - "actions": [ - { - "id": "cycle-state-mlr4bgf0-u7gv-to-state-mlr4dvjl-fyw3", - "label": "to ON", - "from": "state-mlr4bgf0-u7gv", - "to": "state-mlr4dvjl-fyw3" - }, - { - "id": "cycle-state-mlr4dvjl-fyw3-to-state-mlr4bgf0-u7gv", - "label": "to OFF", - "from": "state-mlr4dvjl-fyw3", - "to": "state-mlr4bgf0-u7gv" - } - ], - "transform": { - "position": { - "x": 4.500068482229571, - "y": 1.6470746382091632, - "z": 4.880000008134797 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "lib-mlr4bgf0-u5i4l", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 3.862724577460491, - "y": 0.5565298706483899, - "z": 1.7678768512278153 - } - }, - { - "id": "227cb298980678-19c731c8141", - "title": "Modern tripod floor lamp", - "notes": "Modern tripod floor lamp with a fabric drum shade\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "ON", - "interactions": [ - { - "id": "cycle-state-default-to-state-mlrpcv6c-bdvt", - "label": "to OFF", - "to": "state-mlrpcv6c-bdvt" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "lamp-leg-1", - "type": "cylinder", - "name": "Leg 1", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": 0.125, - "y": 0.675, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.185 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-leg-2", - "type": "cylinder", - "name": "Leg 2", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": -0.0625, - "y": 0.675, - "z": 0.108 - }, - "rotation": { - "x": -0.16, - "y": 0, - "z": -0.092 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-leg-3", - "type": "cylinder", - "name": "Leg 3", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": -0.0625, - "y": 0.675, - "z": -0.108 - }, - "rotation": { - "x": 0.16, - "y": 0, - "z": -0.092 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-neck", - "type": "cylinder", - "name": "Lamp Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 1.38, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-shade", - "type": "cylinder", - "name": "Fabric Shade", - "dimensions": { - "radiusTop": 0.22, - "radiusBottom": 0.22, - "height": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 1.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F2E8D5", - "softness": 0.9, - "metalness": 0, - "transmission": 0.2, - "thickness": 0.01, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "ior": 1.45, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-bulb", - "type": "sphere", - "name": "Light Bulb", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 1.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "emissive": "#FFF4E0", - "emissiveIntensity": 2, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [ - { - "id": "lamp-point-light", - "type": "point", - "color": "#FFF4E0", - "intensity": 4, - "position": { - "x": 0, - "y": 1.5, - "z": 0 - }, - "distance": 6, - "castShadow": true, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - } - } - ], - "groups": [ - { - "id": "tripod-floor-lamp", - "name": "Modern Tripod Floor Lamp", - "children": [ - "lamp-leg-1", - "lamp-leg-2", - "lamp-leg-3", - "lamp-neck", - "lamp-shade", - "lamp-bulb" - ] - } - ] - } - }, - { - "id": "state-mlrpcv6c-bdvt", - "name": "OFF", - "scene": { - "tags": [], - "primitives": [ - { - "id": "lamp-leg-1", - "type": "cylinder", - "name": "Leg 1", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": 0.125, - "y": 0.675, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.185 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-leg-2", - "type": "cylinder", - "name": "Leg 2", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": -0.0625, - "y": 0.675, - "z": 0.108 - }, - "rotation": { - "x": -0.16000000000000006, - "y": -3.469446951953614e-18, - "z": -0.09200000000000003 - }, - "scale": { - "x": 0.9999999999999998, - "y": 1, - "z": 0.9999999999999998 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-leg-3", - "type": "cylinder", - "name": "Leg 3", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 1.38 - }, - "transform": { - "position": { - "x": -0.0625, - "y": 0.675, - "z": -0.108 - }, - "rotation": { - "x": 0.16000000000000006, - "y": 3.469446951953614e-18, - "z": -0.09200000000000003 - }, - "scale": { - "x": 0.9999999999999998, - "y": 1, - "z": 0.9999999999999998 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-neck", - "type": "cylinder", - "name": "Lamp Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 1.38, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-shade", - "type": "cylinder", - "name": "Fabric Shade", - "dimensions": { - "radiusTop": 0.22, - "radiusBottom": 0.22, - "height": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 1.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F2E8D5", - "softness": 0.9, - "metalness": 0, - "transmission": 0.2, - "thickness": 0.01, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "ior": 1.45, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "lamp-bulb", - "type": "sphere", - "name": "Light Bulb", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 1.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "emissive": "#FFF4E0", - "emissiveIntensity": 2, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "tripod-floor-lamp", - "name": "Modern Tripod Floor Lamp", - "children": [ - "lamp-leg-1", - "lamp-leg-2", - "lamp-leg-3", - "lamp-neck", - "lamp-shade", - "lamp-bulb" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlrpcv6c-bdvt-to-state-default", - "label": "to ON", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlrpcv6c-bdvt", - "label": "to OFF", - "from": "state-default", - "to": "state-mlrpcv6c-bdvt" - }, - { - "id": "cycle-state-mlrpcv6c-bdvt-to-state-default", - "label": "to ON", - "from": "state-mlrpcv6c-bdvt", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": 2.567363667122909, - "y": 0.9266403965704086, - "z": 0.7462406515396548 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq38tdj-vp4j", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.021859171998984822, - "y": 0.8218468760394194, - "z": 0 - } - }, - { - "id": "3b515be13388f-19c73217ec3", - "title": "Wooden bookcase", - "notes": "Tall open-shelf wooden bookcase with a minimalist design\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "bk-side-l", - "type": "box", - "name": "Left Support", - "dimensions": { - "width": 0.03, - "height": 1.8, - "depth": 0.3 - }, - "transform": { - "position": { - "x": -0.385, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "generateTexture": "light oak wood grain", - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-side-r", - "type": "box", - "name": "Right Support", - "dimensions": { - "width": 0.03, - "height": 1.8, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0.385, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "generateTexture": "light oak wood grain", - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-shelf-1", - "type": "box", - "name": "Shelf Bottom", - "dimensions": { - "width": 0.74, - "height": 0.03, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.09999999999999998, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-shelf-2", - "type": "box", - "name": "Shelf 2", - "dimensions": { - "width": 0.74, - "height": 0.03, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-shelf-3", - "type": "box", - "name": "Shelf 3", - "dimensions": { - "width": 0.74, - "height": 0.03, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-shelf-4", - "type": "box", - "name": "Shelf 4", - "dimensions": { - "width": 0.74, - "height": 0.03, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 1.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bk-shelf-5", - "type": "box", - "name": "Shelf Top", - "dimensions": { - "width": 0.74, - "height": 0.03, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 1.7, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c341f", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "minimalist-bookcase-group", - "name": "Minimalist Bookcase", - "children": [ - "bk-side-l", - "bk-side-r", - "bk-shelf-1", - "bk-shelf-2", - "bk-shelf-3", - "bk-shelf-4", - "bk-shelf-5" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.2346250672621193, - "y": 1.0120243146713455, - "z": 0.21597170828388723 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.2804687702858426, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsjmbto-c2ea", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.8999999999999999, - "z": 0 - } - }, - { - "id": "0d3779109193e-19c73221b10", - "title": "bookshelf decor", - "notes": "small bookshelf decor set: a stack of 6-8 assorted books, a small ceramic vase, and a minimal abstract sculpture, sized to fit on a shelf", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "decor-book-1", - "type": "box", - "name": "Book Base", - "dimensions": { - "width": 0.22, - "height": 0.04, - "depth": 0.18 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4a5568", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(182%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(216%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-2", - "type": "box", - "name": "Book Stack 2", - "dimensions": { - "width": 0.2, - "height": 0.03, - "depth": 0.16 - }, - "transform": { - "position": { - "x": 0.01, - "y": 0.055, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#718096", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-3", - "type": "box", - "name": "Book Stack 3", - "dimensions": { - "width": 0.21, - "height": 0.035, - "depth": 0.17 - }, - "transform": { - "position": { - "x": -0.01, - "y": 0.0875, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": -0.03, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a0aec0", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-4", - "type": "box", - "name": "Book Top", - "dimensions": { - "width": 0.18, - "height": 0.025, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0, - "y": 0.1175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#787878", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1.2, - "repeatY": 1.1, - "offsetX": 0.07, - "offsetY": 0, - "rotationDeg": 90 - }, - "texturePath": "textures/a2ba694ad53e.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-5", - "type": "box", - "name": "Standing Book 1", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.1, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c05621", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(99%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(133%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-6", - "type": "box", - "name": "Standing Book 2", - "dimensions": { - "width": 0.04, - "height": 0.22, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0.19, - "y": 0.11, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2c5282", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(222%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(256%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-book-7", - "type": "box", - "name": "Standing Book 3", - "dimensions": { - "width": 0.025, - "height": 0.18, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0.225, - "y": 0.09, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#276749", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-vase", - "type": "cylinder", - "name": "Ceramic Vase", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.045, - "height": 0.16 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.08, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#b4b16a", - "softness": 0.25, - "hardness": 0.55, - "roughness": 0.25, - "fluffiness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0 - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-sculpture-base", - "type": "box", - "name": "Sculpture Base", - "dimensions": { - "width": 0.06, - "height": 0.02, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.35, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1a202c", - "softness": 0.2, - "metalness": 0.1, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "decor-sculpture-ring", - "type": "torus", - "name": "Abstract Ring", - "dimensions": { - "radius": 0.05, - "tube": 0.012 - }, - "transform": { - "position": { - "x": 0.35, - "y": 0.06241842753671305, - "z": 0 - }, - "rotation": { - "x": 1.9679980059901385, - "y": 0.04854592575428254, - "z": -1.457674868320746 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#d4af37", - "softness": 0.1, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "bookshelf-decor-set", - "name": "Bookshelf Decor Set", - "children": [ - "decor-book-1", - "decor-book-2", - "decor-book-3", - "decor-book-4", - "decor-book-5", - "decor-book-6", - "decor-book-7", - "decor-vase", - "decor-sculpture-base", - "decor-sculpture-ring" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.2161971652872015, - "y": 0.7367178620425292, - "z": 0.21783707347967085 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrcgos0-nys1", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.08705190594401346, - "y": 0.10999999791383742, - "z": -0.0016066725690125835 - } - }, - { - "id": "d76362662545e-19c73234c4d", - "title": "Books", - "notes": "small stack of hardcover books, neutral colors, realistic, suitable for bookshelf decor", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "book-bottom", - "type": "box", - "name": "Bottom Book", - "dimensions": { - "width": 0.22, - "height": 0.035, - "depth": 0.16, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#160a38", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-middle", - "type": "box", - "name": "Middle Book", - "dimensions": { - "width": 0.2, - "height": 0.03, - "depth": 0.15, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.01, - "y": 0.05, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#708090", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-top", - "type": "box", - "name": "Top Book", - "dimensions": { - "width": 0.21, - "height": 0.032, - "depth": 0.155, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.005, - "y": 0.081, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": -0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3f0e1f", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "book-stack-group", - "name": "Book Stack", - "children": [ - "book-bottom", - "book-middle", - "book-top" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.4493133008996957, - "y": 1.4775923795984425, - "z": 0.2510956471722847 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrch3i5-0258", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0012008581219888764, - "y": 0.04850000034272671, - "z": -0.0021056519411290844 - } - }, - { - "id": "578967ee4707-19c73238f45", - "title": "Hardcover books 1", - "notes": "decorative stack of 5 hardcover books for a bookshelf, muted colors, realistic", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "book-1", - "type": "box", - "name": "Bottom Book", - "dimensions": { - "width": 0.24, - "height": 0.045, - "depth": 0.18, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0225, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4A4E69", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-2", - "type": "box", - "name": "Second Book", - "dimensions": { - "width": 0.22, - "height": 0.035, - "depth": 0.16, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.005, - "y": 0.0625, - "z": -0.005 - }, - "rotation": { - "x": 0, - "y": -0.04, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9A8C98", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-3", - "type": "box", - "name": "Third Book", - "dimensions": { - "width": 0.25, - "height": 0.05, - "depth": 0.19, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.008, - "y": 0.105, - "z": 0.003 - }, - "rotation": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#852914", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-4", - "type": "box", - "name": "Fourth Book", - "dimensions": { - "width": 0.21, - "height": 0.03, - "depth": 0.15, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.002, - "y": 0.145, - "z": 0.007 - }, - "rotation": { - "x": 0, - "y": -0.03, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#22223B", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-5", - "type": "box", - "name": "Top Book", - "dimensions": { - "width": 0.23, - "height": 0.04, - "depth": 0.17, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0.18, - "z": -0.002 - }, - "rotation": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#632808", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0.05, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.65, - "textureHardness": 0.15, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0 - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "book-stack-1", - "name": "Decorative Book Stack", - "children": [ - "book-1", - "book-2", - "book-3", - "book-4", - "book-5" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.8614216537918351, - "y": 1.14, - "z": 0.26 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5707963267948966 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrciack-2heo", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.006083928007335378, - "y": 0.09999999932944774, - "z": 0.0030000000000000027 - } - }, - { - "id": "1a72e35e19a66-19c73244ffc", - "title": "Small potted plant", - "notes": "small potted plant for bookshelf decor, simple ceramic pot, realistic", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "pot-base", - "type": "cylinder", - "name": "Ceramic Pot", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.045, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04720741289489995, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3e0909", - "softness": 1, - "metalness": 0, - "clearcoat": 0.85, - "roughness": 1, - "hardness": 1, - "fluffiness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0.08, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-soil", - "type": "cylinder", - "name": "Soil", - "dimensions": { - "radiusTop": 0.055, - "radiusBottom": 0.055, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.09, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2B1D0E", - "softness": 1, - "textureHardness": 0.8, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "receiveShadow": true, - "physics": true, - "castShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leaf-1", - "type": "box", - "name": "Leaf 1", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.005 - }, - "transform": { - "position": { - "x": 0.01, - "y": 0.18, - "z": 0.01 - }, - "rotation": { - "x": 0.1, - "y": 0.4, - "z": 0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2D5A27", - "softness": 0.5, - "doubleSided": true, - "generateTexture": "snake plant leaf pattern", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(196%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(230%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leaf-2", - "type": "box", - "name": "Leaf 2", - "dimensions": { - "width": 0.025, - "height": 0.16, - "depth": 0.005 - }, - "transform": { - "position": { - "x": -0.02, - "y": 0.16, - "z": -0.01 - }, - "rotation": { - "x": -0.15, - "y": 2.1, - "z": -0.1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3A6B35", - "softness": 0.5, - "doubleSided": true, - "generateTexture": "snake plant leaf pattern", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leaf-3", - "type": "box", - "name": "Leaf 3", - "dimensions": { - "width": 0.025, - "height": 0.22, - "depth": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.19, - "z": -0.02 - }, - "rotation": { - "x": -0.05, - "y": -1.2, - "z": 0.02 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2D5A27", - "softness": 0.5, - "doubleSided": true, - "generateTexture": "snake plant leaf pattern", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leaf-4", - "type": "box", - "name": "Leaf 4", - "dimensions": { - "width": 0.03, - "height": 0.14, - "depth": 0.005 - }, - "transform": { - "position": { - "x": 0.02, - "y": 0.15, - "z": -0.01 - }, - "rotation": { - "x": 0.2, - "y": 0.8, - "z": 0.15 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3A6B35", - "softness": 0.5, - "doubleSided": true, - "generateTexture": "snake plant leaf pattern", - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "small-potted-plant", - "name": "Small Potted Plant", - "children": [ - "pot-base", - "pot-soil", - "leaf-1", - "leaf-2", - "leaf-3", - "leaf-4" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.4087380833018583, - "y": 1.184871881798844, - "z": 0.25297856262312113 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrcguad-gze5", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.14893394079338146, - "z": 0 - } - }, - { - "id": "22db76c93c7648-19c7324b0a5", - "title": "Hardcover books", - "notes": "small stack of hardcover books, muted colors, realistic, sized to sit on a bookshelf shelf", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "book-1-cover", - "type": "box", - "name": "Bottom Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.016, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#314101", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-1-pages", - "type": "box", - "name": "Bottom Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": 0.002, - "y": 0.016, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-2-cover", - "type": "box", - "name": "Middle Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.005, - "y": 0.048, - "z": -0.005 - }, - "rotation": { - "x": 0, - "y": 0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#455A64", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-2-pages", - "type": "box", - "name": "Middle Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": 0.007, - "y": 0.048, - "z": -0.005 - }, - "rotation": { - "x": 0, - "y": 0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-3-cover", - "type": "box", - "name": "Top Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.01, - "y": 0.08, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": -0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#7E736D", - "softness": 0.7, - "metalness": 0.1, - "generateTexture": "vintage cloth book cover texture", - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(201%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(235%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-3-pages", - "type": "box", - "name": "Top Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": -0.008, - "y": 0.08, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": -0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "book-stack-group", - "name": "Stack of Books", - "children": [ - "book-1-cover", - "book-1-pages", - "book-2-cover", - "book-2-pages", - "book-3-cover", - "book-3-pages" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.552216185656096, - "y": 0.29590875727765287, - "z": 0.24483101995851997 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrchjx3-jixt", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.004628634243017425, - "y": 0.048, - "z": 0.003862298942239717 - } - }, - { - "id": "28f8380d2b909-19c7325a1fe", - "title": "Queen size bed", - "notes": "Queen size bed with tufted headboard and layered bedding\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "bed-frame-base", - "type": "box", - "name": "Bed Frame", - "dimensions": { - "width": 1.55, - "height": 0.2, - "depth": 2.05, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.09999999999999998, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#59371d", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "bed-headboard-tufted", - "type": "box", - "name": "Tufted Headboard", - "dimensions": { - "width": 1.6, - "height": 1.2, - "depth": 0.15, - "edgeRadius": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": -1.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#59371d", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "bed-mattress-core", - "type": "box", - "name": "Mattress", - "dimensions": { - "width": 1.5, - "height": 0.25, - "depth": 2, - "edgeRadius": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": -0.025000000000000022 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.8, - "metalness": 0, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "bed-duvet-cover", - "type": "box", - "name": "Duvet", - "dimensions": { - "width": 1.54, - "height": 0.06, - "depth": 1.7, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4552259890157253, - "z": 0.15000000000000002 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.14833937005226464, - "z": 1 - } - }, - "material": { - "color": "#858585", - "softness": 0.88, - "metalness": 0, - "roughness": 0.88, - "hardness": 0, - "fluffiness": 1, - "specularIntensity": 0, - "specularColor": "#ffffff", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.96, - "textureHardness": 0.2, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/84fdd8bbff5e.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "bed-throw-blanket", - "type": "box", - "name": "Throw Blanket", - "dimensions": { - "width": 1.56, - "height": 0.02, - "depth": 0.6, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45473534010854766, - "z": 0.6664777624807252 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.9938979431403886, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#707070", - "softness": 0.88, - "metalness": 0, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.5, - "textureHardness": 0.2, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/c83879b7f455.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "bed-queen-set", - "name": "Queen Bed Set", - "children": [ - "bed-frame-base", - "bed-headboard-tufted", - "bed-mattress-core", - "bed-duvet-cover", - "bed-throw-blanket" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.893117299804146, - "y": 0.68, - "z": -2.45 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq325si-qxp1", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.6000000000000001, - "z": -0.07500001341104512 - } - }, - { - "id": "c73ce00cad78a8-19c732af8bf", - "title": "Bedside table", - "notes": "Small wooden bedside table with two drawers and metal handles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "Closed", - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsm2hfh-kpff", - "label": "to Open", - "to": "state-mlsm2hfh-kpff" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "bedside-body", - "type": "box", - "name": "Main Cabinet", - "dimensions": { - "width": 0.45, - "height": 0.55, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-top", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-bottom", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-top", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-bottom", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "bedside-table-group", - "name": "Bedside Table", - "children": [ - "bedside-body", - "bedside-drawer-top", - "bedside-drawer-bottom", - "bedside-handle-top", - "bedside-handle-bottom" - ] - } - ] - } - }, - { - "id": "state-mlsm2hfh-kpff", - "name": "Open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "bedside-body", - "type": "box", - "name": "Main Cabinet", - "dimensions": { - "width": 0.45, - "height": 0.55, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "f05e318f0458f-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 1.1041926220978733, - 0, - 0, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 0, - 0, - 0.07443495604140436, - 0, - 0.004530832198648017, - -0.15255109405892034, - -0.010014872971582059, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "bedside-drawer-top", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.4332191393880365 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-bottom", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-top", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.44321913938803653 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-bottom", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "78a1da78d8ac18-19c72e3273a", - "type": "box", - "name": "Top Drawer Face copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.0009927581746976832, - "y": 0.2971844151440166, - "z": 0.31966498683917194 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "88dbe11b137958-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 1.1041926220978733, - 0, - 0, - 0, - 0, - 2.5091220304588343e-16, - 0.07443495604140436, - 0, - 0, - -1.1300081041401508, - 1.6527880406825704e-17, - 0, - 0.003434635946619483, - -0.12748252516057204, - 0.01377937627176781, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "879e8cc107e628-19c72e3e27f", - "type": "box", - "name": "Top Drawer Face copy copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.19531840413377144, - "y": 0.40031399562824094, - "z": 0.23098145332672942 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "1c67f50863beb8-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 2.4518001453485667e-16, - 0, - -0.07443495604140436, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 1.1041926220978733, - 0, - 1.6527880406825704e-17, - 0, - 0.2201999730030892, - -0.010945263436824646, - 0.0071782213531727404, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "d12ea0fc82e53-19c72e460b5", - "type": "box", - "name": "Top Drawer Face copy copy copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.20183595859435438, - "y": 0.40031399562824094, - "z": 0.23098145332672942 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "fbffd01bfaec1-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 2.4518001453485667e-16, - 0, - -0.07443495604140436, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 1.1041926220978733, - 0, - 1.6527880406825704e-17, - 0, - -0.21833494415528995, - -0.010945263436824646, - 0.007178221353172742, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - } - ], - "lights": [], - "groups": [ - { - "id": "0a812fb3c92f28-19c72e52ddc", - "name": "Group", - "children": [ - "bedside-body", - "bedside-drawer-top", - "bedside-drawer-bottom", - "bedside-handle-top", - "bedside-handle-bottom", - "78a1da78d8ac18-19c72e3273a", - "879e8cc107e628-19c72e3e27f", - "d12ea0fc82e53-19c72e460b5", - "7b0075257f0cf8-19c72e4a69c" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsm2hfh-kpff-to-state-default", - "label": "to Closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsm2hfh-kpff", - "label": "to Open", - "from": "state-default", - "to": "state-mlsm2hfh-kpff" - }, - { - "id": "cycle-state-mlsm2hfh-kpff-to-state-default", - "label": "to Closed", - "from": "state-mlsm2hfh-kpff", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -5.64, - "y": 0.39, - "z": -1.2651768042674925 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.5019873160601556, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq32xv9-s9d3", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.275, - "z": 0.01249999839812517 - } - }, - { - "id": "392a951bb6f11-19c732bcfa3", - "title": "Bedside table", - "notes": "Small wooden bedside table with two drawers and metal handles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "Closed", - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsm2hfh-kpff", - "label": "to Open", - "to": "state-mlsm2hfh-kpff" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "bedside-body", - "type": "box", - "name": "Main Cabinet", - "dimensions": { - "width": 0.45, - "height": 0.55, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-top", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-bottom", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-top", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-bottom", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "bedside-table-group", - "name": "Bedside Table", - "children": [ - "bedside-body", - "bedside-drawer-top", - "bedside-drawer-bottom", - "bedside-handle-top", - "bedside-handle-bottom" - ] - } - ] - } - }, - { - "id": "state-mlsm2hfh-kpff", - "name": "Open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "bedside-body", - "type": "box", - "name": "Main Cabinet", - "dimensions": { - "width": 0.45, - "height": 0.55, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "f05e318f0458f-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 1.1041926220978733, - 0, - 0, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 0, - 0, - 0.07443495604140436, - 0, - 0.004530832198648017, - -0.15255109405892034, - -0.010014872971582059, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "bedside-drawer-top", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.4332191393880365 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-drawer-bottom", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-top", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.44321913938803653 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bedside-handle-bottom", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.12, - "height": 0.015, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0.215 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "78a1da78d8ac18-19c72e3273a", - "type": "box", - "name": "Top Drawer Face copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.0009927581746976832, - "y": 0.2971844151440166, - "z": 0.31966498683917194 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "88dbe11b137958-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 1.1041926220978733, - 0, - 0, - 0, - 0, - 2.5091220304588343e-16, - 0.07443495604140436, - 0, - 0, - -1.1300081041401508, - 1.6527880406825704e-17, - 0, - 0.003434635946619483, - -0.12748252516057204, - 0.01377937627176781, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "879e8cc107e628-19c72e3e27f", - "type": "box", - "name": "Top Drawer Face copy copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.19531840413377144, - "y": 0.40031399562824094, - "z": 0.23098145332672942 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "1c67f50863beb8-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 2.4518001453485667e-16, - 0, - -0.07443495604140436, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 1.1041926220978733, - 0, - 1.6527880406825704e-17, - 0, - 0.2201999730030892, - -0.010945263436824646, - 0.0071782213531727404, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - }, - { - "id": "d12ea0fc82e53-19c72e460b5", - "type": "box", - "name": "Top Drawer Face copy copy copy", - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.20183595859435438, - "y": 0.40031399562824094, - "z": 0.23098145332672942 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a57869", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [ - { - "id": "fbffd01bfaec1-19c732a57cd", - "type": "box", - "targetToSourceMatrix": [ - 2.4518001453485667e-16, - 0, - -0.07443495604140436, - 0, - 0, - 1.1300081041401508, - 0, - 0, - 1.1041926220978733, - 0, - 1.6527880406825704e-17, - 0, - -0.21833494415528995, - -0.010945263436824646, - 0.007178221353172742, - 1 - ], - "dimensions": { - "width": 0.41, - "height": 0.23, - "depth": 0.01 - } - } - ] - } - ], - "lights": [], - "groups": [ - { - "id": "0a812fb3c92f28-19c72e52ddc", - "name": "Group", - "children": [ - "bedside-body", - "bedside-drawer-top", - "bedside-drawer-bottom", - "bedside-handle-top", - "bedside-handle-bottom", - "78a1da78d8ac18-19c72e3273a", - "879e8cc107e628-19c72e3e27f", - "d12ea0fc82e53-19c72e460b5", - "7b0075257f0cf8-19c72e4a69c" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsm2hfh-kpff-to-state-default", - "label": "to Closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsm2hfh-kpff", - "label": "to Open", - "from": "state-default", - "to": "state-mlsm2hfh-kpff" - }, - { - "id": "cycle-state-mlsm2hfh-kpff-to-state-default", - "label": "to Closed", - "from": "state-mlsm2hfh-kpff", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -5.628831317188218, - "y": 0.372181049562469, - "z": -3.622031341394607 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.5, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq32xv9-s9d3", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.275, - "z": 0.01249999839812517 - } - }, - { - "id": "d1b8634521f5b-19c732c7954", - "title": "Hardcover books", - "notes": "small stack of hardcover books, muted colors, realistic, sized to sit on a bookshelf shelf", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "book-1-cover", - "type": "box", - "name": "Bottom Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.016, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#314101", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-1-pages", - "type": "box", - "name": "Bottom Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": 0.002, - "y": 0.016, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-2-cover", - "type": "box", - "name": "Middle Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.005, - "y": 0.048, - "z": -0.005 - }, - "rotation": { - "x": 0, - "y": 0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#455A64", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-2-pages", - "type": "box", - "name": "Middle Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": 0.007, - "y": 0.048, - "z": -0.005 - }, - "rotation": { - "x": 0, - "y": 0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-3-cover", - "type": "box", - "name": "Top Book Cover", - "dimensions": { - "width": 0.16, - "height": 0.032, - "depth": 0.23, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.01, - "y": 0.08, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": -0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#7E736D", - "softness": 0.7, - "metalness": 0.1, - "generateTexture": "vintage cloth book cover texture", - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(201%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(235%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-3-pages", - "type": "box", - "name": "Top Book Pages", - "dimensions": { - "width": 0.155, - "height": 0.028, - "depth": 0.225 - }, - "transform": { - "position": { - "x": -0.008, - "y": 0.08, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": -0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FAF9F6", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "book-stack-group", - "name": "Stack of Books", - "children": [ - "book-1-cover", - "book-1-pages", - "book-2-cover", - "book-2-pages", - "book-3-cover", - "book-3-pages" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.536708061318499, - "y": 0.45461773108196735, - "z": -3.702951078012202 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrchjx3-jixt", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.004628634243017425, - "y": 0.048, - "z": 0.003862298942239717 - } - }, - { - "id": "cffc759645ff18-19c732db260", - "title": "Books", - "notes": "small stack of hardcover books, neutral colors, realistic, suitable for bookshelf decor", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "book-bottom", - "type": "box", - "name": "Bottom Book", - "dimensions": { - "width": 0.22, - "height": 0.035, - "depth": 0.16, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#160a38", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-middle", - "type": "box", - "name": "Middle Book", - "dimensions": { - "width": 0.2, - "height": 0.03, - "depth": 0.15, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0.01, - "y": 0.05, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0.12, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#708090", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-top", - "type": "box", - "name": "Top Book", - "dimensions": { - "width": 0.21, - "height": 0.032, - "depth": 0.155, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": -0.005, - "y": 0.081, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": -0.08, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3f0e1f", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "book-stack-group", - "name": "Book Stack", - "children": [ - "book-bottom", - "book-middle", - "book-top" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.580755687496764, - "y": 0.4376603227117231, - "z": -1.2697435441517564 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.0849412180967812, - "y": 0.77, - "z": 0.77 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrch3i5-0258", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0012008581219888764, - "y": 0.04850000034272671, - "z": -0.0021056519411290844 - } - }, - { - "id": "bc6db6f4cbd0a-19c732ef8c8", - "title": "Modern Office Work Desk", - "notes": "Modern home office desk with an ergonomic mesh swivel chair\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "desk-top", - "type": "box", - "name": "Desk Top", - "dimensions": { - "width": 1.4, - "height": 0.03, - "depth": 0.7, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.735, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#6d4737", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "desk-leg-fl", - "type": "cylinder", - "name": "Desk Leg FL", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.72 - }, - "transform": { - "position": { - "x": -0.65, - "y": 0.36, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.8, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "desk-leg-fr", - "type": "cylinder", - "name": "Desk Leg FR", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.72 - }, - "transform": { - "position": { - "x": 0.65, - "y": 0.36, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.8, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "desk-leg-bl", - "type": "cylinder", - "name": "Desk Leg BL", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.72 - }, - "transform": { - "position": { - "x": -0.65, - "y": 0.36, - "z": -0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.8, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "desk-leg-br", - "type": "cylinder", - "name": "Desk Leg BR", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.72 - }, - "transform": { - "position": { - "x": 0.65, - "y": 0.36, - "z": -0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.8, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "7f21c7e5ec623-19c72f34765", - "name": "Desk", - "children": [ - "desk-top", - "desk-leg-fl", - "desk-leg-fr", - "desk-leg-bl", - "desk-leg-br" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.7301176406514844, - "y": 0.4654592368201924, - "z": -0.43338758405308886 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq34z9t-arju", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.37499999267980455, - "z": 0 - } - }, - { - "id": "5028f5326c4f1-19c73300d7f", - "title": "Work Chair", - "notes": "Saved asset", - "states": [ - { - "id": "state-mlrqdudw-h1ax", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "865634a68ff0c8-19c6fa60e71", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.21942266224756057, - "y": -0.05873483430121462, - "z": -1.3748734225649277 - }, - "rotation": { - "x": 2.9816909120018336, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.7162987977628072, - "y": 1, - "z": 0.03279146505941484 - } - }, - "material": { - "color": "#1f1f1f", - "roughness": 0.88, - "softness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "metalness": 0, - "specularIntensity": 0.35, - "specularColor": "#928787", - "envMapIntensity": 0.2, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "alphaCutoff": 0, - "textureSoftness": 0.5, - "textureHardness": 0.2, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 2.8, - "repeatY": 2.9, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/f29666a3db99.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "8135caa097bd2-19c6fa9acac", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.22, - "y": -0.549953396220423, - "z": -0.9027449963735288 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.73, - "y": 0.7847111321752007, - "z": 0.03475878894563731 - } - }, - "material": { - "color": "#121212", - "roughness": 0.92, - "softness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "metalness": 0, - "specularIntensity": 0.25, - "specularColor": "#969696", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "alphaCutoff": 0, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 2.6, - "repeatY": 1.8, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "6547e60f44d2c8-19c6fad7591", - "type": "box", - "name": "Box copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.09711870865800565, - "y": -0.3557109650390796, - "z": -0.9104222442785064 - }, - "rotation": { - "x": 1.5707963267948966, - "y": -1.0408340855860843e-17, - "z": 0.05346834479492222 - }, - "scale": { - "x": 0.08313951875983154, - "y": 0.7847111321752007, - "z": 0.055468998567793575 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "7c74141cfb3f38-19c6fae01ef", - "type": "box", - "name": "Box copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.5261849380677825, - "y": -0.3557109650390796, - "z": -0.9134882245150125 - }, - "rotation": { - "x": 1.5707963267948961, - "y": 6.938893903907228e-18, - "z": -0.022771642166586045 - }, - "scale": { - "x": 0.08313951875983154, - "y": 0.784711132175201, - "z": 0.05546899856779356 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "4a6bcc5b026328-19c6faebc5d", - "type": "box", - "name": "Box copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.07671241863482464, - "y": -0.45064142494280335, - "z": -0.5564654193684817 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.08, - "y": 0.20057068493450145, - "z": 0.06 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "1b7fcbb9d8b61-19c6fb05a04", - "type": "box", - "name": "Box copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.514798789744797, - "y": -0.45064142494280335, - "z": -0.5564654193684817 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.08, - "y": 0.20057068493450145, - "z": 0.06 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "16028c014b9e9-19c6fb11dad", - "type": "box", - "name": "Box copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.24, - "y": -1.1031495048669495, - "z": -0.91 - }, - "rotation": { - "x": 1.570796326794896, - "y": 5.551115123125783e-17, - "z": 0.785398163397448 - }, - "scale": { - "x": 0.08, - "y": 0.7799999999999997, - "z": 0.05999999999999999 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "222d821342865-19c6fb179dd", - "type": "box", - "name": "Box copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.07, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.24, - "y": -1.1, - "z": -0.91 - }, - "rotation": { - "x": 1.570796326794896, - "y": -5.551115123125783e-17, - "z": -0.785398163397448 - }, - "scale": { - "x": 0.08, - "y": 0.7799999999999997, - "z": 0.05999999999999999 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "76547e0ddb7898-19c6fb1be82", - "type": "cylinder", - "name": "Cylinder", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radiusTop": 0.5, - "radiusBottom": 0.5, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": -0.2361800252222828, - "y": -0.81999710037277, - "z": -0.9212067306198259 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0667566866827826, - "y": 0.5381416311085973, - "z": 0.0667566866827826 - } - }, - "material": { - "color": "#000000", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#000000", - "envMapIntensity": 0.9, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#000000", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.55, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "77a436a6a22f88-19c6fb35dd1", - "type": "sphere", - "name": "Sphere", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "widthSegments": 32, - "heightSegments": 16, - "phiStartDeg": 0, - "phiLengthDeg": 360, - "thetaStartDeg": 0, - "thetaLengthDeg": 180 - }, - "transform": { - "position": { - "x": 8.68852640537876e-05, - "y": -1.1507226206277827, - "z": -0.6695831572437114 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05367480268133374, - "y": 0.05367480268133374, - "z": 0.05367480268133374 - } - }, - "material": { - "color": "#000000", - "roughness": 0.95, - "softness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "metalness": 0, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0.05, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "alphaCutoff": 0, - "textureSoftness": 0.65, - "textureHardness": 0.15, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "4941e34246c0a8-19c6fb4fae9", - "type": "sphere", - "name": "Sphere copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "widthSegments": 32, - "heightSegments": 16, - "phiStartDeg": 0, - "phiLengthDeg": 360, - "thetaStartDeg": 0, - "thetaLengthDeg": 180 - }, - "transform": { - "position": { - "x": -0.4767681899471271, - "y": -1.1507226206277827, - "z": -0.6695831572437114 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05367480268133374, - "y": 0.05367480268133374, - "z": 0.05367480268133374 - } - }, - "material": { - "color": "#000000", - "roughness": 0.95, - "softness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "metalness": 0, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0.05, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "alphaCutoff": 0, - "textureSoftness": 0.65, - "textureHardness": 0.15, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "49f5b20786da7-19c6fb547f9", - "type": "sphere", - "name": "Sphere copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "widthSegments": 32, - "heightSegments": 16, - "phiStartDeg": 0, - "phiLengthDeg": 360, - "thetaStartDeg": 0, - "thetaLengthDeg": 180 - }, - "transform": { - "position": { - "x": 0.0056859942536856, - "y": -1.149509887854384, - "z": -1.1511054087288661 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05367480268133374, - "y": 0.05367480268133374, - "z": 0.05367480268133374 - } - }, - "material": { - "color": "#000000", - "roughness": 0.95, - "softness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "metalness": 0, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0.05, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "alphaCutoff": 0, - "textureSoftness": 0.65, - "textureHardness": 0.15, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "7d2077eff952e-19c6fb64e84", - "type": "sphere", - "name": "Sphere copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "widthSegments": 32, - "heightSegments": 16, - "phiStartDeg": 0, - "phiLengthDeg": 360, - "thetaStartDeg": 0, - "thetaLengthDeg": 180 - }, - "transform": { - "position": { - "x": -0.475449084300519, - "y": -1.1424906839491518, - "z": -1.1569276899712062 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.05367480268133374, - "y": 0.05367480268133374, - "z": 0.05367480268133374 - } - }, - "material": { - "color": "#000000", - "roughness": 0.95, - "softness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "metalness": 0, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0.05, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "alphaCutoff": 0, - "textureSoftness": 0.65, - "textureHardness": 0.15, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "4ea487c202d818-19c6fb8d569", - "name": "Work Chair", - "children": [ - "865634a68ff0c8-19c6fa60e71", - "8135caa097bd2-19c6fa9acac", - "6547e60f44d2c8-19c6fad7591", - "7c74141cfb3f38-19c6fae01ef", - "4a6bcc5b026328-19c6faebc5d", - "1b7fcbb9d8b61-19c6fb05a04", - "16028c014b9e9-19c6fb11dad", - "222d821342865-19c6fb179dd", - "77a436a6a22f88-19c6fb35dd1", - "76547e0ddb7898-19c6fb1be82", - "4941e34246c0a8-19c6fb4fae9", - "49f5b20786da7-19c6fb547f9", - "7d2077eff952e-19c6fb64e84" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-mlrqdudw-h1ax", - "actions": [], - "transform": { - "position": { - "x": -1.6718826945870606, - "y": 0.6166519299880042, - "z": -1.539229575343378 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6439314083963319, - "y": 0.6439314083963319, - "z": 0.6439314083963319 - } - }, - "pickable": false, - "bumpable": true, - "bumpResponse": 0.65, - "bumpDamping": 0.77, - "libraryAssetId": "lib-mlrqdudw-40nr9", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.21270116532945352, - "y": -0.3700314207695614, - "z": -0.9905300125758202 - } - }, - { - "id": "ac7a816a60265-19c73317f64", - "title": "Arc floor lamp", - "notes": "Arc floor lamp with a brushed metal finish and large shade\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "ON", - "scene": { - "tags": [], - "primitives": [ - { - "id": "arc-lamp-base", - "type": "cylinder", - "name": "Lamp Base", - "dimensions": { - "radiusTop": 0.18, - "radiusBottom": 0.18, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.020000000000000018, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "generateTexture": "brushed steel", - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-stem-v", - "type": "cylinder", - "name": "Vertical Stem", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 1.5 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-arm-1", - "type": "cylinder", - "name": "Arc Arm Segment 1", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.28, - "y": 1.82, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -0.8000000000000002 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-arm-2", - "type": "cylinder", - "name": "Arc Arm Segment 2", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.96, - "y": 2.1, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-shade", - "type": "cone", - "name": "Lamp Shade", - "dimensions": { - "radius": 0.25, - "height": 0.2 - }, - "transform": { - "position": { - "x": 1.36, - "y": 2.011494584765661, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4b93b", - "softness": 1, - "emissive": "#FFFFFF", - "emissiveIntensity": 0.1, - "roughness": 1, - "hardness": 0.55, - "fluffiness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1, - "thickness": 0, - "attenuationColor": "#440808", - "attenuationDistance": 0.01, - "iridescence": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.1, - "repeatY": 2.7, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-bulb", - "type": "sphere", - "name": "Light Bulb", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": 1.36, - "y": 1.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFEE", - "emissive": "#FFFFAA", - "emissiveIntensity": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": false, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [ - { - "id": "arc-lamp-light", - "type": "spot", - "color": "#FFFFAA", - "intensity": 6.7, - "position": { - "x": 1.36, - "y": 1.85, - "z": 0 - }, - "target": { - "x": 1.36, - "y": -3.15, - "z": 0 - }, - "distance": 5, - "angle": 1, - "penumbra": 0.5, - "castShadow": true, - "name": "Spot Light", - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - } - ], - "groups": [ - { - "id": "ad517e370bdee-19c72baa5aa", - "name": "Group", - "children": [ - "arc-lamp-base", - "arc-lamp-stem-v", - "arc-lamp-arm-1", - "arc-lamp-arm-2" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlskixi4-xebf", - "label": "to OFF", - "to": "state-mlskixi4-xebf" - } - ] - }, - { - "id": "state-mlskixi4-xebf", - "name": "OFF", - "scene": { - "tags": [], - "primitives": [ - { - "id": "arc-lamp-base", - "type": "cylinder", - "name": "Lamp Base", - "dimensions": { - "radiusTop": 0.18, - "radiusBottom": 0.18, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.020000000000000018, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "generateTexture": "brushed steel", - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-stem-v", - "type": "cylinder", - "name": "Vertical Stem", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 1.5 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-arm-1", - "type": "cylinder", - "name": "Arc Arm Segment 1", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.28, - "y": 1.82, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -0.8000000000000002 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-arm-2", - "type": "cylinder", - "name": "Arc Arm Segment 2", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.96, - "y": 2.1, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1f1f1f", - "metalness": 0.95, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-shade", - "type": "cone", - "name": "Lamp Shade", - "dimensions": { - "radius": 0.25, - "height": 0.2 - }, - "transform": { - "position": { - "x": 1.36, - "y": 2.011494584765661, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4b93b", - "softness": 1, - "emissive": "#FFFFFF", - "emissiveIntensity": 0.1, - "roughness": 1, - "hardness": 0.55, - "fluffiness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1, - "thickness": 0, - "attenuationColor": "#440808", - "attenuationDistance": 0.01, - "iridescence": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.1, - "repeatY": 2.7, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "arc-lamp-bulb", - "type": "sphere", - "name": "Light Bulb", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": 1.36, - "y": 1.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFEE", - "emissive": "#FFFFAA", - "emissiveIntensity": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": false, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ad517e370bdee-19c72baa5aa", - "name": "Group", - "children": [ - "arc-lamp-base", - "arc-lamp-stem-v", - "arc-lamp-arm-1", - "arc-lamp-arm-2" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlskixi4-xebf-to-state-default", - "label": "to ON", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-mlskixi4-xebf", - "actions": [ - { - "id": "cycle-state-default-to-state-mlskixi4-xebf", - "label": "to OFF", - "from": "state-default", - "to": "state-mlskixi4-xebf" - }, - { - "id": "cycle-state-mlskixi4-xebf-to-state-default", - "label": "to ON", - "from": "state-mlskixi4-xebf", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -2.333111809397252, - "y": 0.8985525485842618, - "z": -0.4373821778992827 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.7651227023559565, - "y": 0.7651227023559565, - "z": 0.7651227023559565 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsjk8i3-g9fb", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.7149999964237214, - "y": 1.055747293351406, - "z": 0 - } - }, - { - "id": "1765e77823bda8-19c7334745f", - "title": "Large wardrobe", - "notes": "Large three-door wooden wardrobe with integrated full-length mirror\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsmklqi-mykf", - "label": "to Open", - "to": "state-mlsmklqi-mykf" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "wardrobe-body", - "type": "box", - "name": "Wardrobe Main Body", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 1.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-kickplate", - "type": "box", - "name": "Wardrobe Kickplate", - "dimensions": { - "width": 1.8, - "height": 0.1, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.050000000000000044, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-left", - "type": "box", - "name": "Left Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.6, - "y": 1.1, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-middle", - "type": "box", - "name": "Middle Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 1.1, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-right", - "type": "box", - "name": "Right Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.6, - "y": 1.1, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-mirror", - "type": "box", - "name": "Full Length Mirror", - "dimensions": { - "width": 0.45, - "height": 1.8, - "depth": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 1.1, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0, - "metalness": 1, - "envMapIntensity": 2.4, - "roughness": 0, - "hardness": 1, - "fluffiness": 0, - "specularIntensity": 1.7, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2.2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0, - "textureSoftness": 0, - "textureHardness": 1, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-left", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": -0.35, - "y": 1.1, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-middle", - "type": "cylinder", - "name": "Middle Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.25, - "y": 1.1, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-right", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.35, - "y": 1.1, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "wardrobe-large-3door", - "name": "Large Three-Door Wardrobe", - "children": [ - "wardrobe-body", - "wardrobe-kickplate", - "wardrobe-door-left", - "wardrobe-door-middle", - "wardrobe-door-right", - "wardrobe-mirror", - "wardrobe-handle-left", - "wardrobe-handle-middle", - "wardrobe-handle-right" - ] - } - ] - } - }, - { - "id": "state-mlsmklqi-mykf", - "name": "Open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wardrobe-body", - "type": "box", - "name": "Wardrobe Main Body", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0.2933135739478811, - "y": 1.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6616761671707818, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-kickplate", - "type": "box", - "name": "Wardrobe Kickplate", - "dimensions": { - "width": 1.8, - "height": 0.1, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.00032593869269892384, - "y": 0.050000000000000044, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-left", - "type": "box", - "name": "Left Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.8626272313530059, - "y": 1.0971597431953797, - "z": 0.5839405925234058 - }, - "rotation": { - "x": 0, - "y": -1.527440302024895, - "z": 0 - }, - "scale": { - "x": 0.9999999999999999, - "y": 1, - "z": 0.9999999999999999 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-middle", - "type": "box", - "name": "Middle Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.00032593869269892384, - "y": 1.1, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-door-right", - "type": "box", - "name": "Right Door", - "dimensions": { - "width": 0.59, - "height": 2, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.599674061307301, - "y": 1.1, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-mirror", - "type": "box", - "name": "Full Length Mirror", - "dimensions": { - "width": 0.45, - "height": 1.8, - "depth": 0.005 - }, - "transform": { - "position": { - "x": -0.00032593869269892384, - "y": 1.1, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0, - "metalness": 1, - "envMapIntensity": 2.4, - "roughness": 0, - "hardness": 1, - "fluffiness": 0, - "specularIntensity": 1.7, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2.2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0, - "textureSoftness": 0, - "textureHardness": 1, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-left", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": -0.8717728260979429, - "y": 1.0971597431953797, - "z": 0.8345725100773387 - }, - "rotation": { - "x": 0, - "y": -1.527440302024895, - "z": 0 - }, - "scale": { - "x": 0.9999999999999999, - "y": 1, - "z": 0.9999999999999999 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-middle", - "type": "cylinder", - "name": "Middle Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.24967406130730113, - "y": 1.1, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wardrobe-handle-right", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.349674061307301, - "y": 1.1, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9E9E9E", - "metalness": 0, - "softness": 0.96, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "70fefe0e3e1b78-19c72eeaca3", - "type": "box", - "name": "Wardrobe Main Body copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.5873754533621945, - "y": 1.05, - "z": -0.2591863408779823 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.3256138252326135, - "y": 1, - "z": 0.11642436529869168 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "29eadcb969d898-19c72ef3fe7", - "type": "box", - "name": "Wardrobe Main Body copy copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.8622280371592921, - "y": 1.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.020298479655001485, - "y": 1, - "z": 1.0084993220524192 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "c2d779147749a8-19c72f07365", - "type": "box", - "name": "Wardrobe Main Body copy copy copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.5805794531402156, - "y": 2.070326529383241, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.3146771054281404, - "y": 0.028061995861134184, - "z": 1.0084993220524192 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "47865bb81f2908-19c72f1042b", - "type": "box", - "name": "Wardrobe Main Body copy copy copy copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.5605244268382784, - "y": 0.07080915733835169, - "z": -0.0008182590817130686 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.3146771054281404, - "y": 0.028061995861134184, - "z": 1.0084993220524192 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "d9e486920a3b68-19c72f16245", - "type": "box", - "name": "Wardrobe Main Body copy copy copy copy copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.5798760467481743, - "y": 1.3700840816042703, - "z": -0.0008182590817130686 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.3146771054281404, - "y": 0.028061995861134184, - "z": 1.0084993220524192 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "4931c3a85f17e8-19c72f190ae", - "type": "box", - "name": "Wardrobe Main Body copy copy copy copy copy copy", - "dimensions": { - "width": 1.8, - "height": 2.1, - "depth": 0.58 - }, - "transform": { - "position": { - "x": -0.5721390752342965, - "y": 0.7239130063903627, - "z": -0.0008182590817130686 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.3146771054281404, - "y": 0.028061995861134184, - "z": 1.0084993220524192 - } - }, - "material": { - "color": "#4E342E", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "69f20f54c57af8-19c72f20983", - "name": "Large Three-Door Wardrobe", - "children": [ - "wardrobe-body", - "wardrobe-kickplate", - "wardrobe-door-left", - "wardrobe-door-middle", - "wardrobe-door-right", - "wardrobe-mirror", - "wardrobe-handle-left", - "wardrobe-handle-middle", - "wardrobe-handle-right", - "70fefe0e3e1b78-19c72eeaca3", - "29eadcb969d898-19c72ef3fe7", - "c2d779147749a8-19c72f07365", - "47865bb81f2908-19c72f1042b", - "d9e486920a3b68-19c72f16245", - "4931c3a85f17e8-19c72f190ae" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsmklqi-mykf-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsmklqi-mykf", - "label": "to Open", - "from": "state-default", - "to": "state-mlsmklqi-mykf" - }, - { - "id": "cycle-state-mlsmklqi-mykf-to-state-default", - "label": "to closed", - "from": "state-mlsmklqi-mykf", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -0.31533036247765267, - "y": 1.1536078665542793, - "z": -4.546448663974232 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3917312529711456, - "y": 1, - "z": 1.091767035307015 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq33y2q-tepv", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 1.0499999996274711, - "z": 0.02000000406056643 - } - }, - { - "id": "6eaff768b565c-19c7335c107", - "title": "Freestanding oval soaking bathtub with chrome fl...", - "notes": "Freestanding oval soaking bathtub with chrome floor-mounted faucet\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "Empty", - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsiig1y-6e45", - "label": "to Full", - "to": "state-mlsiig1y-6e45" - } - ], - "scene": { - "tags": [], - "primitives": [ - { - "id": "faucet-base", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.05, - "radiusBottom": 0.05, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.5548159025607885, - "y": 0.7629623154632806, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "envMapIntensity": 1.5, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-stand", - "type": "cylinder", - "name": "Faucet Stand", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 1 - }, - "transform": { - "position": { - "x": -0.5521449259073273, - "y": 0.8388170042115098, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.13684592609847324, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-neck", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.5016844575611338, - "y": 0.9107747918750824, - "z": 4.154836345395038 - }, - "rotation": { - "x": 1.5707963267948966, - "y": -3.673205103646371e-06, - "z": -1.5707963267948963 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-spout", - "type": "cylinder", - "name": "Faucet Spout", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.4352701513115653, - "y": 0.884209069375255, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "b7359f3af6326-19c728138c7", - "type": "cylinder", - "name": "tub outside", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radiusTop": 0.47, - "radiusBottom": 0.31, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": 0.08813499016471649, - "y": 0.535584049934831, - "z": 4.231659860618338 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.4669340775238464, - "y": 0.4437677174542672, - "z": 1 - } - }, - "material": { - "color": "#808080", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.34, - "specularColor": "#ffffff", - "envMapIntensity": 3, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0.45, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "f9f8114269d3c-19c7281c6a4", - "type": "cylinder", - "name": "inside", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radiusTop": 0.47, - "radiusBottom": 0.31, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": 0.08145607322002102, - "y": 0.6078797569125278, - "z": 4.2352265520004835 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3, - "y": 0.3, - "z": 0.8698589044399397 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.95, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.35, - "specularColor": "#ffffff", - "envMapIntensity": 1.6, - "opacity": 0.96, - "transmission": 1, - "ior": 1.333, - "thickness": 0.4, - "attenuationColor": "#ffffff", - "attenuationDistance": 0.55, - "iridescence": 0.12, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0.03, - "alphaCutoff": 0, - "textureSoftness": 0.08, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "69ec634c1b0628-19c7287c4b7", - "type": "cylinder", - "name": "Faucet Stand", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 1 - }, - "transform": { - "position": { - "x": -0.5604674762217019, - "y": 0.8327738756471842, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.13684592609847324, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "db56d15ba8ceb-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.05, - "radiusBottom": 0.05, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.5604674762217019, - "y": 0.756919186898955, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "envMapIntensity": 1.5, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "c2bf1e30f9cac8-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.507336031222047, - "y": 0.9047316633107567, - "z": 4.249197225407204 - }, - "rotation": { - "x": 1.5707963267948966, - "y": -3.6732051034243265e-06, - "z": -1.5707963267948961 - }, - "scale": { - "x": 0.5313144499965471, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "5a84af8e12d0b8-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Spout", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.4409217249724786, - "y": 0.8781659408109294, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "32a946af95e63-19c7286833a", - "name": "Faucet cold", - "children": [ - "faucet-stand", - "faucet-base", - "faucet-neck", - "faucet-spout" - ], - "pickable": false - }, - { - "id": "ec6d69ccf8bf6-19c7287c4b8", - "name": "Faucet hot", - "children": [ - "69ec634c1b0628-19c7287c4b7", - "db56d15ba8ceb-19c7287c4b8", - "c2bf1e30f9cac8-19c7287c4b8", - "5a84af8e12d0b8-19c7287c4b8" - ], - "pickable": false - } - ] - } - }, - { - "id": "state-mlsiig1y-6e45", - "name": "Full", - "scene": { - "tags": [], - "primitives": [ - { - "id": "faucet-base", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.05, - "radiusBottom": 0.05, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.5548159025607885, - "y": 0.7629623154632806, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "envMapIntensity": 1.5, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-stand", - "type": "cylinder", - "name": "Faucet Stand", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 1 - }, - "transform": { - "position": { - "x": -0.5521449259073273, - "y": 0.8388170042115098, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.13684592609847324, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-neck", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.5016844575611338, - "y": 0.9107747918750824, - "z": 4.154836345395038 - }, - "rotation": { - "x": 1.5707963267948966, - "y": -3.673205103646371e-06, - "z": -1.5707963267948963 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-spout", - "type": "cylinder", - "name": "Faucet Spout", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.4352701513115653, - "y": 0.884209069375255, - "z": 4.154836345395038 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "b7359f3af6326-19c728138c7", - "type": "cylinder", - "name": "tub outside", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radiusTop": 0.47, - "radiusBottom": 0.31, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": 0.08813499016471649, - "y": 0.535584049934831, - "z": 4.231659860618338 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.4669340775238464, - "y": 0.4437677174542672, - "z": 1 - } - }, - "material": { - "color": "#808080", - "roughness": 0.35, - "softness": 0.35, - "hardness": 0.35, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.34, - "specularColor": "#ffffff", - "envMapIntensity": 3, - "opacity": 1, - "transmission": 0, - "ior": 1.47, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0.45, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0.2, - "alphaCutoff": 0, - "textureSoftness": 0.2, - "textureHardness": 0.65, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "f9f8114269d3c-19c7281c6a4", - "type": "cylinder", - "name": "inside", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radiusTop": 0.47, - "radiusBottom": 0.31, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": 0.08145607322002102, - "y": 0.6078797569125278, - "z": 4.2352265520004835 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3, - "y": 0.3, - "z": 0.8698589044399397 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.95, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.35, - "specularColor": "#ffffff", - "envMapIntensity": 1.6, - "opacity": 0.96, - "transmission": 1, - "ior": 1.333, - "thickness": 0.4, - "attenuationColor": "#237bcd", - "attenuationDistance": 0.55, - "iridescence": 0.12, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 1, - "clearcoatRoughness": 0.03, - "alphaCutoff": 0, - "textureSoftness": 0.08, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "69ec634c1b0628-19c7287c4b7", - "type": "cylinder", - "name": "Faucet Stand", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 1 - }, - "transform": { - "position": { - "x": -0.5604674762217019, - "y": 0.8327738756471842, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.13684592609847324, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "db56d15ba8ceb-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.05, - "radiusBottom": 0.05, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.5604674762217019, - "y": 0.756919186898955, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "envMapIntensity": 1.5, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "c2bf1e30f9cac8-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.507336031222047, - "y": 0.9047316633107567, - "z": 4.249197225407204 - }, - "rotation": { - "x": 1.5707963267948966, - "y": -3.6732051034243265e-06, - "z": -1.5707963267948961 - }, - "scale": { - "x": 0.5313144499965471, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "5a84af8e12d0b8-19c7287c4b8", - "type": "cylinder", - "name": "Faucet Spout", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.4409217249724786, - "y": 0.8781659408109294, - "z": 4.249197225407204 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5313144499965473, - "y": 0.5313144499965473, - "z": 0.5313144499965473 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "32a946af95e63-19c7286833a", - "name": "Faucet cold", - "children": [ - "faucet-stand", - "faucet-base", - "faucet-neck", - "faucet-spout" - ], - "pickable": false - }, - { - "id": "ec6d69ccf8bf6-19c7287c4b8", - "name": "Faucet hot", - "children": [ - "69ec634c1b0628-19c7287c4b7", - "db56d15ba8ceb-19c7287c4b8", - "c2bf1e30f9cac8-19c7287c4b8", - "5a84af8e12d0b8-19c7287c4b8" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsiig1y-6e45-to-state-default", - "label": "to Empty", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsiig1y-6e45", - "label": "to Full", - "from": "state-default", - "to": "state-mlsiig1y-6e45" - }, - { - "id": "cycle-state-mlsiig1y-6e45-to-state-default", - "label": "to Empty", - "from": "state-mlsiig1y-6e45", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": 4.522408021754294, - "y": 0.3904049244978556, - "z": -0.896791648070859 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1.7873490005753465, - "y": 1, - "z": 1.3568453847354858 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq30q9r-g5ay", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.08813499016471649, - "y": 0.6175507578992459, - "z": 4.231659860618338 - } - }, - { - "id": "b500b00d33e638-19c735e91d7", - "title": "refrigerator", - "notes": "Modern stainless steel double-door refrigerator with water dispenser\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fridge-body", - "type": "box", - "name": "Main Body", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": -0.3598941677364852 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.11447498354301384 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-door-left", - "type": "box", - "name": "Left Door", - "dimensions": { - "width": 0.44, - "height": 1.15, - "depth": 0.05 - }, - "transform": { - "position": { - "x": -0.225, - "y": 1.225, - "z": 0.325 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "generateTexture": "brushed stainless steel texture", - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.2, - "height": 0.3, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "fridge-dispenser-back", - "type": "box", - "name": "Dispenser Interior", - "dimensions": { - "width": 0.18, - "height": 0.28, - "depth": 0.04 - }, - "transform": { - "position": { - "x": -0.225, - "y": 1.225, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.1, - "metalness": 0.2, - "emissive": "#002244", - "emissiveIntensity": 0.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-door-right", - "type": "box", - "name": "Right Door", - "dimensions": { - "width": 0.44, - "height": 1.15, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0.225, - "y": 1.225, - "z": 0.325 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-freezer-drawer", - "type": "box", - "name": "Freezer Drawer", - "dimensions": { - "width": 0.89, - "height": 0.55, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0.32500000000000007, - "z": 0.325 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": -0.05, - "y": 1.2, - "z": 0.38 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.05, - "y": 1.2, - "z": 0.38 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-freezer", - "type": "cylinder", - "name": "Freezer Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.7 - }, - "transform": { - "position": { - "x": 0, - "y": 0.5, - "z": 0.38 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "1330cb12762958-19c7349cc6a", - "type": "box", - "name": "Main Body copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.4062943053218938, - "y": 0.9004598013056943, - "z": -0.04864111990579628 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.09855645210101832, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a4cf4830a46d8-19c734a6a25", - "type": "box", - "name": "Main Body copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.004983719796238351, - "y": 1.7579935776736242, - "z": -0.05283176244052312 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.04750692031721514, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "160ca29831a418-19c734af740", - "type": "box", - "name": "Main Body copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": 0.40180529257736186, - "y": 0.9004598013056943, - "z": -0.04864111990579628 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.09855645210101832, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "62bc117c7967a-19c734beb43", - "type": "box", - "name": "Main Body copy copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.004319108408693151, - "y": 0.6407881156548036, - "z": -0.04290648292873958 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.04750692031721514, - "z": 0.9819573274101506 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a08610d8c87db8-19c734d66bc", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -0.4565337012956655, - "y": 0.9031500704694377, - "z": -0.04613032039916648 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.35155132565042907, - "y": 0.8899203033347398, - "z": 1 - } - }, - "material": { - "color": "#000000", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "bd985ba23be5e-19c734f2fbe", - "type": "plane", - "name": "Plane copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 0.4500427682790401, - "y": 0.908174354187991, - "z": -0.04613032039916648 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.35155132565042907, - "y": 0.8899203033347398, - "z": 1 - } - }, - "material": { - "color": "#000000", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "0a03e12b2da838-19c734fa5e0", - "name": "Fridge", - "children": [ - "fridge-body", - "fridge-door-left", - "fridge-dispenser-back", - "fridge-door-right", - "fridge-freezer-drawer", - "fridge-handle-l", - "fridge-handle-r", - "fridge-handle-freezer", - "1330cb12762958-19c7349cc6a", - "a4cf4830a46d8-19c734a6a25", - "160ca29831a418-19c734af740", - "62bc117c7967a-19c734beb43", - "a08610d8c87db8-19c734d66bc", - "bd985ba23be5e-19c734f2fbe" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsqs928-jonu", - "label": "to Open", - "to": "state-mlsqs928-jonu" - } - ] - }, - { - "id": "state-mlsqs928-jonu", - "name": "Open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fridge-body", - "type": "box", - "name": "Main Body", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": -0.3598941677364852 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.11447498354301384 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-door-left", - "type": "box", - "name": "Left Door", - "dimensions": { - "width": 0.44, - "height": 1.15, - "depth": 0.05 - }, - "transform": { - "position": { - "x": -0.4129238703004095, - "y": 1.23, - "z": 0.5538140123634258 - }, - "rotation": { - "x": -3.141592653589793, - "y": -1.3992944001552596, - "z": -3.141592653589793 - }, - "scale": { - "x": 1.0000000000000002, - "y": 1, - "z": 1.0000000000000002 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "generateTexture": "brushed stainless steel texture", - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.2, - "height": 0.3, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "fridge-door-right", - "type": "box", - "name": "Right Door", - "dimensions": { - "width": 0.44, - "height": 1.15, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0.4470563706056322, - "y": 1.22, - "z": 0.519701366581902 - }, - "rotation": { - "x": -3.141592653589793, - "y": 1.1564473559153596, - "z": -3.141592653589793 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-freezer-drawer", - "type": "box", - "name": "Freezer Drawer", - "dimensions": { - "width": 0.89, - "height": 0.55, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0.32500000000000007, - "z": 0.325 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#585656", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": -0.4858991812090622, - "y": 1.2, - "z": 0.7061859876365743 - }, - "rotation": { - "x": 3.141592653589793, - "y": -1.0502285497563937, - "z": 3.141592653589793 - }, - "scale": { - "x": 1.0000000000000004, - "y": 1, - "z": 1.0000000000000004 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.5480935810023155, - "y": 1.2, - "z": 0.6802986334180979 - }, - "rotation": { - "x": -3.141592653589793, - "y": 1.1564473559153596, - "z": -3.141592653589793 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fridge-handle-freezer", - "type": "cylinder", - "name": "Freezer Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.7 - }, - "transform": { - "position": { - "x": 0, - "y": 0.5, - "z": 0.38 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "1330cb12762958-19c7349cc6a", - "type": "box", - "name": "Main Body copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.4062943053218938, - "y": 0.9004598013056943, - "z": -0.04864111990579628 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.09855645210101832, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a4cf4830a46d8-19c734a6a25", - "type": "box", - "name": "Main Body copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.004983719796238351, - "y": 1.7579935776736242, - "z": -0.05283176244052312 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.04750692031721514, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "160ca29831a418-19c734af740", - "type": "box", - "name": "Main Body copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": 0.40180529257736186, - "y": 0.9004598013056943, - "z": -0.04864111990579628 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.09855645210101832, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "62bc117c7967a-19c734beb43", - "type": "box", - "name": "Main Body copy copy copy", - "dimensions": { - "width": 0.9, - "height": 1.8, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.004319108408693151, - "y": 0.6407881156548036, - "z": -0.04290648292873958 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.04750692031721514, - "z": 0.9819573274101506 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a08610d8c87db8-19c734d66bc", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -0.4565337012956655, - "y": 0.9031500704694377, - "z": -0.04613032039916648 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.35155132565042907, - "y": 0.8899203033347398, - "z": 1 - } - }, - "material": { - "color": "#000000", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "bd985ba23be5e-19c734f2fbe", - "type": "plane", - "name": "Plane copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 0.4500427682790401, - "y": 0.908174354187991, - "z": -0.04613032039916648 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.35155132565042907, - "y": 0.8899203033347398, - "z": 1 - } - }, - "material": { - "color": "#000000", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "75df9a7b0a7f48-19c7355212f", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.0013169456414679759, - "y": 0.7059287289462521, - "z": 0.02039527715842976 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.699006702839821, - "y": -0.017990441521080584, - "z": 0.6073771715455583 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "50749e0bd2173-19c7356e66d", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.004229974598174313, - "y": 1.0316642722925293, - "z": 0.02039527715842976 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.699006702839821, - "y": -0.017990441521080584, - "z": 0.6073771715455583 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "3822301a017c58-19c73575f98", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.0057533622967674836, - "y": 1.4064061263762135, - "z": 0.02039527715842976 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.699006702839821, - "y": -0.017990441521080584, - "z": 0.6073771715455583 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "8e26f1ba0267d-19c7359aa9c", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.33961853388617713, - "y": 0.7558469733807619, - "z": 0.579583617452367 - }, - "rotation": { - "x": -2.118398765389043e-17, - "y": -0.17922864958353102, - "z": -1.989465825821058e-18 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.3566243072768484 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "ff08d47272dcf-19c735b462f", - "type": "box", - "name": "Box copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.3364470450092423, - "y": 1.11816471541013, - "z": 0.579583617452367 - }, - "rotation": { - "x": -2.118398765389043e-17, - "y": -0.17922864958353102, - "z": -1.989465825821058e-18 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.3566243072768484 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "e4ece85cb17ea8-19c735ba45e", - "type": "box", - "name": "Box copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.33421545338427355, - "y": 1.4713227449680928, - "z": 0.579583617452367 - }, - "rotation": { - "x": -2.118398765389043e-17, - "y": -0.17922864958353102, - "z": -1.989465825821058e-18 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.3566243072768484 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "7338412175949-19c735c033f", - "type": "box", - "name": "Box copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.37950011778832243, - "y": 1.4931517624322264, - "z": 0.552662533729948 - }, - "rotation": { - "x": 0, - "y": 0.4035738107124245, - "z": 0 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.36 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "0aaa973885a4d-19c735ce4e9", - "type": "box", - "name": "Box copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.39073002811881696, - "y": 1.163519809656926, - "z": 0.552662533729948 - }, - "rotation": { - "x": 0, - "y": 0.4035738107124245, - "z": 0 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.36 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "31953df25409b8-19c735d1cf7", - "type": "box", - "name": "Box copy copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.3740179764296412, - "y": 0.7009626247112464, - "z": 0.5478735015701741 - }, - "rotation": { - "x": 0, - "y": 0.4035738107124245, - "z": 0 - }, - "scale": { - "x": 0.12, - "y": 0.01, - "z": 0.36 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.02, - "softness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "envMapIntensity": 1.5, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - } - ], - "lights": [], - "groups": [ - { - "id": "a12bfeaf5a9ab-19c735e0443", - "name": "Sleek Wooden TV Stand", - "children": [ - "fridge-body", - "fridge-door-left", - "fridge-door-right", - "fridge-freezer-drawer", - "fridge-handle-l", - "fridge-handle-r", - "fridge-handle-freezer", - "1330cb12762958-19c7349cc6a", - "a4cf4830a46d8-19c734a6a25", - "160ca29831a418-19c734af740", - "62bc117c7967a-19c734beb43", - "a08610d8c87db8-19c734d66bc", - "bd985ba23be5e-19c734f2fbe", - "75df9a7b0a7f48-19c7355212f", - "50749e0bd2173-19c7356e66d", - "3822301a017c58-19c73575f98", - "8e26f1ba0267d-19c7359aa9c", - "ff08d47272dcf-19c735b462f", - "e4ece85cb17ea8-19c735ba45e", - "7338412175949-19c735c033f", - "0aaa973885a4d-19c735ce4e9", - "31953df25409b8-19c735d1cf7" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsqs928-jonu-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsqs928-jonu", - "label": "to Open", - "from": "state-default", - "to": "state-mlsqs928-jonu" - }, - { - "id": "cycle-state-mlsqs928-jonu-to-state-default", - "label": "to closed", - "from": "state-mlsqs928-jonu", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -2.6390041499833767, - "y": 0.9876455923302134, - "z": 0.5193998030296436 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspk5rb-3xfn", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.0032454665083127, - "y": 0.9003749143341613, - "z": -0.003915878407667384 - } - }, - { - "id": "669fe9f9f97588-19c736a9260", - "title": "kitchen base cabinet", - "notes": "Modern wooden kitchen base cabinet with drawers and metal handles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.024999999999999994 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "6fd2224bf71f68-19c7366b851", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "caedcf27bdf418-19c736643c7", - "a3e9fa0c9f39-19c736501f8" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "to": "state-mlsr95pw-kpf4" - } - ] - }, - { - "id": "state-mlsr95pw-kpf4", - "name": "open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.025000000000000022 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6788421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6988421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a64fa7aca12ab-19c7367e20c", - "type": "box", - "name": "Middle Drawer Face copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.27598325992034456, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "26692db1df4e1-19c73684b3a", - "type": "box", - "name": "Middle Drawer Face copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.28332317257126993, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "ec22289d68bd6-19c73687c74", - "type": "box", - "name": "Middle Drawer Face copy copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.004872213024542521, - "y": 0.4283249307441317, - "z": 0.29241684590147965 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.933814689919458, - "y": 2.707674922349371, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "8d462a00bf618-19c736a092b", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "a3e9fa0c9f39-19c736501f8", - "caedcf27bdf418-19c736643c7", - "a64fa7aca12ab-19c7367e20c", - "26692db1df4e1-19c73684b3a", - "ec22289d68bd6-19c73687c74" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "from": "state-default", - "to": "state-mlsr95pw-kpf4" - }, - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "from": "state-mlsr95pw-kpf4", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -3.6717847955793625, - "y": 0.529275586728742, - "z": 0.4720761398832385 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.8098083678676107, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspuje4-k4jw", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.0004609774656834098, - "y": 0.4399999994598329, - "z": 0.01919808945853102 - } - }, - { - "id": "a4013b97f887b8-19c736d15ce", - "title": "kitchen base cabinet", - "notes": "Modern wooden kitchen base cabinet with drawers and metal handles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.024999999999999994 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "6fd2224bf71f68-19c7366b851", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "caedcf27bdf418-19c736643c7", - "a3e9fa0c9f39-19c736501f8" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "to": "state-mlsr95pw-kpf4" - } - ] - }, - { - "id": "state-mlsr95pw-kpf4", - "name": "open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.025000000000000022 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6788421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6988421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a64fa7aca12ab-19c7367e20c", - "type": "box", - "name": "Middle Drawer Face copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.27598325992034456, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "26692db1df4e1-19c73684b3a", - "type": "box", - "name": "Middle Drawer Face copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.28332317257126993, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "ec22289d68bd6-19c73687c74", - "type": "box", - "name": "Middle Drawer Face copy copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.004872213024542521, - "y": 0.4283249307441317, - "z": 0.29241684590147965 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.933814689919458, - "y": 2.707674922349371, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "8d462a00bf618-19c736a092b", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "a3e9fa0c9f39-19c736501f8", - "caedcf27bdf418-19c736643c7", - "a64fa7aca12ab-19c7367e20c", - "26692db1df4e1-19c73684b3a", - "ec22289d68bd6-19c73687c74" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "from": "state-default", - "to": "state-mlsr95pw-kpf4" - }, - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "from": "state-mlsr95pw-kpf4", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -5.578769058377267, - "y": 0.5351143306110472, - "z": 4.341854069829404 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.7788682026204685, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspuje4-k4jw", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.0004609774656834098, - "y": 0.4399999994598329, - "z": 0.01919808945853102 - } - }, - { - "id": "414821fb84733-19c736ec63b", - "title": "kitchen base cabinet", - "notes": "Modern wooden kitchen base cabinet with drawers and metal handles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "closed", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.024999999999999994 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "6fd2224bf71f68-19c7366b851", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "caedcf27bdf418-19c736643c7", - "a3e9fa0c9f39-19c736501f8" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "to": "state-mlsr95pw-kpf4" - } - ] - }, - { - "id": "state-mlsr95pw-kpf4", - "name": "open", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mbc-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0.2827700239098009, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.04999999999999999, - "z": -0.025000000000000022 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-countertop", - "type": "box", - "name": "Countertop", - "dimensions": { - "width": 0.62, - "height": 0.03, - "depth": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.865, - "z": 0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.3, - "metalness": 0.1, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-1", - "type": "box", - "name": "Top Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.14, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-2", - "type": "box", - "name": "Middle Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6788421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-drawer-3", - "type": "box", - "name": "Bottom Drawer Face", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.31 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-1", - "type": "box", - "name": "Top Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.77, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-2", - "type": "box", - "name": "Middle Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": 0.6988421719424496 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mbc-handle-3", - "type": "box", - "name": "Bottom Handle", - "dimensions": { - "width": 0.2, - "height": 0.01, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.26, - "z": 0.33 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "99103f86be55c-19c7363ba93", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0015533910567189224, - "y": 0.475, - "z": -0.2702996853701447 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "09a11bc28054e8-19c7363ff18", - "type": "box", - "name": "Cabinet Carcass copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.0032114373379978356, - "y": 0.7708649342695443, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.21726217693525512, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a3e9fa0c9f39-19c736501f8", - "type": "box", - "name": "Cabinet Carcass copy copy copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.010921945394623683, - "y": 0.25148373331191926, - "z": 0.26717350976581034 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.4075297129993522, - "z": 0.08028492709682793 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "caedcf27bdf418-19c736643c7", - "type": "box", - "name": "Cabinet Carcass copy", - "dimensions": { - "width": 0.6, - "height": 0.75, - "depth": 0.6 - }, - "transform": { - "position": { - "x": -0.2905636008162644, - "y": 0.475, - "z": -0.0016038089384916021 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0533220408602473, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "a64fa7aca12ab-19c7367e20c", - "type": "box", - "name": "Middle Drawer Face copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.27598325992034456, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "26692db1df4e1-19c73684b3a", - "type": "box", - "name": "Middle Drawer Face copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.28332317257126993, - "y": 0.55, - "z": 0.3845701674516536 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "ec22289d68bd6-19c73687c74", - "type": "box", - "name": "Middle Drawer Face copy copy copy", - "dimensions": { - "width": 0.59, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.004872213024542521, - "y": 0.4283249307441317, - "z": 0.29241684590147965 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.933814689919458, - "y": 2.707674922349371, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "8d462a00bf618-19c736a092b", - "name": "kitchen base cabinet", - "children": [ - "mbc-carcass", - "mbc-kickplate", - "mbc-countertop", - "mbc-drawer-1", - "mbc-drawer-2", - "mbc-drawer-3", - "mbc-handle-1", - "mbc-handle-2", - "mbc-handle-3", - "99103f86be55c-19c7363ba93", - "09a11bc28054e8-19c7363ff18", - "a3e9fa0c9f39-19c736501f8", - "caedcf27bdf418-19c736643c7", - "a64fa7aca12ab-19c7367e20c", - "26692db1df4e1-19c73684b3a", - "ec22289d68bd6-19c73687c74" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlsr95pw-kpf4", - "label": "to open", - "from": "state-default", - "to": "state-mlsr95pw-kpf4" - }, - { - "id": "cycle-state-mlsr95pw-kpf4-to-state-default", - "label": "to closed", - "from": "state-mlsr95pw-kpf4", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -5.581997975509333, - "y": 0.5282067101293415, - "z": 1.7664241653590549 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.4227205704111852, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspuje4-k4jw", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.0004609774656834098, - "y": 0.4399999994598329, - "z": 0.01919808945853102 - } - }, - { - "id": "80182e12bc169-19c73a10d27", - "title": "Professional Gas Range", - "notes": "Professional gas range with five burners and large convection oven\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "off", - "scene": { - "tags": [], - "primitives": [ - { - "id": "range-main-body", - "type": "box", - "name": "Range Body", - "dimensions": { - "width": 0.9, - "height": 0.85, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 0.425, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8f8f8f", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-door", - "type": "box", - "name": "Oven Door", - "dimensions": { - "width": 0.88, - "height": 0.55, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.29 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#949494", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-window", - "type": "box", - "name": "Oven Window", - "dimensions": { - "width": 0.65, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "opacity": 0.9, - "transmission": 0.8, - "ior": 1.5, - "thickness": 0.01, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-bar", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.75 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": 0.35 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-l", - "type": "cylinder", - "name": "Handle Stem Left", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-r", - "type": "cylinder", - "name": "Handle Stem Right", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-cooktop", - "type": "box", - "name": "Cooktop Surface", - "dimensions": { - "width": 0.9, - "height": 0.05, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.875, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-1", - "type": "cylinder", - "name": "Burner 1", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-2", - "type": "cylinder", - "name": "Burner 2", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-3", - "type": "cylinder", - "name": "Burner 3", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-4", - "type": "cylinder", - "name": "Burner 4", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-5", - "type": "cylinder", - "name": "Burner 5", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-1", - "type": "cylinder", - "name": "Knob 1", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-2", - "type": "cylinder", - "name": "Knob 2", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-3", - "type": "cylinder", - "name": "Knob 3", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-4", - "type": "cylinder", - "name": "Knob 4", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-5", - "type": "cylinder", - "name": "Knob 5", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-back-lip", - "type": "box", - "name": "Backsplash Lip", - "dimensions": { - "width": 0.9, - "height": 0.08, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.94, - "z": -0.28 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "professional-gas-range", - "name": "Professional Gas Range", - "children": [ - "range-main-body", - "range-oven-door", - "range-oven-window", - "range-handle-bar", - "range-handle-stem-l", - "range-handle-stem-r", - "range-cooktop", - "range-burner-1", - "range-burner-2", - "range-burner-3", - "range-burner-4", - "range-burner-5", - "range-knob-1", - "range-knob-2", - "range-knob-3", - "range-knob-4", - "range-knob-5", - "range-back-lip" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlssjm1p-xotl", - "label": "turn on front right", - "to": "state-mlssjm1p-xotl" - }, - { - "id": "it-mlsszajl-asw9", - "label": "turn on back right", - "to": "state-mlsskac7-ywao" - }, - { - "id": "it-mlsszdl0-9396", - "label": "turn on front left", - "to": "state-mlssl1do-1yyl" - } - ] - }, - { - "id": "state-mlssjm1p-xotl", - "name": "front-right-on", - "scene": { - "tags": [], - "primitives": [ - { - "id": "range-main-body", - "type": "box", - "name": "Range Body", - "dimensions": { - "width": 0.9, - "height": 0.85, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 0.425, - "z": -0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8f8f8f", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-door", - "type": "box", - "name": "Oven Door", - "dimensions": { - "width": 0.88, - "height": 0.55, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.29 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#949494", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-window", - "type": "box", - "name": "Oven Window", - "dimensions": { - "width": 0.65, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "opacity": 0.9, - "transmission": 0.8, - "ior": 1.5, - "thickness": 0.01, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-bar", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.75 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": 0.35 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-l", - "type": "cylinder", - "name": "Handle Stem Left", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-r", - "type": "cylinder", - "name": "Handle Stem Right", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-cooktop", - "type": "box", - "name": "Cooktop Surface", - "dimensions": { - "width": 0.9, - "height": 0.05, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.875, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-1", - "type": "cylinder", - "name": "Burner 1", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": -0.15000000000000002 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-2", - "type": "cylinder", - "name": "Burner 2", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-3", - "type": "cylinder", - "name": "Burner 3", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-4", - "type": "cylinder", - "name": "Burner 4", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": -0.15000000000000002 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-5", - "type": "cylinder", - "name": "Burner 5", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8e320b", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-1", - "type": "cylinder", - "name": "Knob 1", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-2", - "type": "cylinder", - "name": "Knob 2", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-3", - "type": "cylinder", - "name": "Knob 3", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-4", - "type": "cylinder", - "name": "Knob 4", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-5", - "type": "cylinder", - "name": "Knob 5", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-back-lip", - "type": "box", - "name": "Backsplash Lip", - "dimensions": { - "width": 0.9, - "height": 0.08, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.94, - "z": -0.28 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "25fb6c52999478-19c73895608", - "type": "cylinder", - "name": "Burner 5 copy", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.299194820380302, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#872c2c", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "8563f01bdecb2-19c738b0d52", - "name": "Professional Gas Range", - "children": [ - "range-main-body", - "range-oven-door", - "range-oven-window", - "range-handle-bar", - "range-handle-stem-l", - "range-handle-stem-r", - "range-cooktop", - "range-burner-1", - "range-burner-2", - "range-burner-3", - "range-burner-4", - "range-burner-5", - "range-knob-1", - "range-knob-2", - "range-knob-3", - "range-knob-4", - "range-knob-5", - "range-back-lip", - "25fb6c52999478-19c73895608" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlssjm1p-xotl-to-state-mlsskac7-ywao", - "label": "turn off", - "to": "state-default" - } - ] - }, - { - "id": "state-mlsskac7-ywao", - "name": "back-right-on", - "scene": { - "tags": [], - "primitives": [ - { - "id": "range-main-body", - "type": "box", - "name": "Range Body", - "dimensions": { - "width": 0.9, - "height": 0.85, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 0.425, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8f8f8f", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-door", - "type": "box", - "name": "Oven Door", - "dimensions": { - "width": 0.88, - "height": 0.55, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.29 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#949494", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-window", - "type": "box", - "name": "Oven Window", - "dimensions": { - "width": 0.65, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "opacity": 0.9, - "transmission": 0.8, - "ior": 1.5, - "thickness": 0.01, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-bar", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.75 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": 0.35 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-l", - "type": "cylinder", - "name": "Handle Stem Left", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-r", - "type": "cylinder", - "name": "Handle Stem Right", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-cooktop", - "type": "box", - "name": "Cooktop Surface", - "dimensions": { - "width": 0.9, - "height": 0.05, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.875, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-1", - "type": "cylinder", - "name": "Burner 1", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-2", - "type": "cylinder", - "name": "Burner 2", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-3", - "type": "cylinder", - "name": "Burner 3", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-4", - "type": "cylinder", - "name": "Burner 4", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8c0808", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-5", - "type": "cylinder", - "name": "Burner 5", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-1", - "type": "cylinder", - "name": "Knob 1", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-2", - "type": "cylinder", - "name": "Knob 2", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-3", - "type": "cylinder", - "name": "Knob 3", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-4", - "type": "cylinder", - "name": "Knob 4", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-5", - "type": "cylinder", - "name": "Knob 5", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-back-lip", - "type": "box", - "name": "Backsplash Lip", - "dimensions": { - "width": 0.9, - "height": 0.08, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.94, - "z": -0.28 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "professional-gas-range", - "name": "Professional Gas Range", - "children": [ - "range-main-body", - "range-oven-door", - "range-oven-window", - "range-handle-bar", - "range-handle-stem-l", - "range-handle-stem-r", - "range-cooktop", - "range-burner-1", - "range-burner-2", - "range-burner-3", - "range-burner-4", - "range-burner-5", - "range-grate-left", - "range-grate-center", - "range-grate-right", - "range-knob-1", - "range-knob-2", - "range-knob-3", - "range-knob-4", - "range-knob-5", - "range-back-lip" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlsskac7-ywao-to-state-mlssl1do-1yyl", - "label": "turn off", - "to": "state-default" - } - ] - }, - { - "id": "state-mlssl1do-1yyl", - "name": "front-left-on", - "scene": { - "tags": [], - "primitives": [ - { - "id": "range-main-body", - "type": "box", - "name": "Range Body", - "dimensions": { - "width": 0.9, - "height": 0.85, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 0.425, - "z": -0.010000000000000009 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8f8f8f", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-door", - "type": "box", - "name": "Oven Door", - "dimensions": { - "width": 0.88, - "height": 0.55, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.29 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#949494", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-oven-window", - "type": "box", - "name": "Oven Window", - "dimensions": { - "width": 0.65, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.375, - "z": 0.311 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "opacity": 0.9, - "transmission": 0.8, - "ior": 1.5, - "thickness": 0.01, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-bar", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.75 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": 0.35 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-l", - "type": "cylinder", - "name": "Handle Stem Left", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-handle-stem-r", - "type": "cylinder", - "name": "Handle Stem Right", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.6, - "z": 0.33 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-cooktop", - "type": "box", - "name": "Cooktop Surface", - "dimensions": { - "width": 0.9, - "height": 0.05, - "depth": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.875, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-1", - "type": "cylinder", - "name": "Burner 1", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": -0.15000000000000002 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-2", - "type": "cylinder", - "name": "Burner 2", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#610a0a", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-3", - "type": "cylinder", - "name": "Burner 3", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.9, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-4", - "type": "cylinder", - "name": "Burner 4", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": -0.15000000000000002 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-burner-5", - "type": "cylinder", - "name": "Burner 5", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.9, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-1", - "type": "cylinder", - "name": "Knob 1", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-2", - "type": "cylinder", - "name": "Knob 2", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-3", - "type": "cylinder", - "name": "Knob 3", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-4", - "type": "cylinder", - "name": "Knob 4", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.16, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-knob-5", - "type": "cylinder", - "name": "Knob 5", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.32, - "y": 0.75, - "z": 0.3 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 1, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "range-back-lip", - "type": "box", - "name": "Backsplash Lip", - "dimensions": { - "width": 0.9, - "height": 0.08, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.94, - "z": -0.28 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "professional-gas-range", - "name": "Professional Gas Range", - "children": [ - "range-main-body", - "range-oven-door", - "range-oven-window", - "range-handle-bar", - "range-handle-stem-l", - "range-handle-stem-r", - "range-cooktop", - "range-burner-1", - "range-burner-2", - "range-burner-3", - "range-burner-4", - "range-burner-5", - "range-grate-left", - "range-grate-center", - "range-grate-right", - "range-knob-1", - "range-knob-2", - "range-knob-3", - "range-knob-4", - "range-knob-5", - "range-back-lip" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlssl1do-1yyl-to-state-default", - "label": "turn off", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-default", - "actions": [ - { - "id": "cycle-state-default-to-state-mlssjm1p-xotl", - "label": "turn on front right", - "from": "state-default", - "to": "state-mlssjm1p-xotl" - }, - { - "id": "it-mlsszajl-asw9", - "label": "turn on back right", - "from": "state-default", - "to": "state-mlsskac7-ywao" - }, - { - "id": "it-mlsszdl0-9396", - "label": "turn on front left", - "from": "state-default", - "to": "state-mlssl1do-1yyl" - }, - { - "id": "cycle-state-mlssjm1p-xotl-to-state-mlsskac7-ywao", - "label": "turn off", - "from": "state-mlssjm1p-xotl", - "to": "state-default" - }, - { - "id": "cycle-state-mlsskac7-ywao-to-state-mlssl1do-1yyl", - "label": "turn off", - "from": "state-mlsskac7-ywao", - "to": "state-default" - }, - { - "id": "cycle-state-mlssl1do-1yyl-to-state-default", - "label": "turn off", - "from": "state-mlssl1do-1yyl", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": -4.746920983597513, - "y": 0.5687589957482349, - "z": 0.3873462875499325 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1219595276194203, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsplhnv-j3al", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.4899999935925007, - "z": 0.030999994091689576 - } - }, - { - "id": "5aca293afb4bc8-19c73a60e13", - "title": "Built-in stainless steel dishwasher", - "notes": "Built-in stainless steel dishwasher with top controls\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "dw-main-chassis", - "type": "box", - "name": "Main Chassis", - "dimensions": { - "width": 0.6, - "height": 0.85, - "depth": 0.58 - }, - "transform": { - "position": { - "x": 0, - "y": 0.425, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-front-door", - "type": "box", - "name": "Front Door Panel", - "dimensions": { - "width": 0.595, - "height": 0.75, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.475, - "z": 0.28 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#363636", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-kickplate", - "type": "box", - "name": "Kickplate", - "dimensions": { - "width": 0.6, - "height": 0.1, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0.24 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-handle-bar", - "type": "cylinder", - "name": "Handle Bar", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.45 - }, - "transform": { - "position": { - "x": 0, - "y": 0.78, - "z": 0.32 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-handle-mount-l", - "type": "box", - "name": "Handle Mount Left", - "dimensions": { - "width": 0.02, - "height": 0.02, - "depth": 0.04 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.78, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-handle-mount-r", - "type": "box", - "name": "Handle Mount Right", - "dimensions": { - "width": 0.02, - "height": 0.02, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.78, - "z": 0.3 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "dw-control-strip", - "type": "box", - "name": "Top Control Strip", - "dimensions": { - "width": 0.5, - "height": 0.02, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.84, - "z": 0.27 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.3, - "metalness": 0.2, - "emissive": "#111111", - "emissiveIntensity": 0.5, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "dishwasher-unit", - "name": "Built-in Dishwasher", - "children": [ - "dw-main-chassis", - "dw-front-door", - "dw-kickplate", - "dw-handle-bar", - "dw-handle-mount-l", - "dw-handle-mount-r", - "dw-control-strip" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.62, - "y": 0.52, - "z": 3.5 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspot3m-r7d9", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.42500000000000004, - "z": 0.016000004224479197 - } - }, - { - "id": "62797796e9767-19c73af66a5", - "title": "Sink", - "notes": "Undermount stainless steel double sink with gooseneck faucet\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "faucet-base", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.2810879724000349, - "y": 1.3042583158958854, - "z": 1.5303285349833975 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D0D0D0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-neck", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.2810879724000349, - "y": 1.4242583158958853, - "z": 1.5303285349833975 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D0D0D0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-curve", - "type": "torus", - "name": "Faucet Arc", - "dimensions": { - "radius": 0.1, - "tube": 0.015 - }, - "transform": { - "position": { - "x": -0.2810879724000349, - "y": 1.5492583158958853, - "z": 1.6303285349833976 - }, - "rotation": { - "x": 0, - "y": 1.5707926536057681, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D0D0D0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-head", - "type": "cylinder", - "name": "Faucet Head", - "dimensions": { - "radiusTop": 0.018, - "radiusBottom": 0.02, - "height": 0.05 - }, - "transform": { - "position": { - "x": -0.2810879724000349, - "y": 1.5242583158958851, - "z": 1.7303285349833977 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D0D0D0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "faucet-handle", - "type": "cylinder", - "name": "Faucet Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.08 - }, - "transform": { - "position": { - "x": -0.22108797240003492, - "y": 1.3242583158958852, - "z": 1.5303285349833975 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D0D0D0", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "68b4bb7fb61378-19c73aa0261", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.26132764037451595, - "y": 0.8033376157250385, - "z": 1.8804847264090667 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.054144352387931916, - "z": 0.788305347568813 - } - }, - "material": { - "color": "#383838", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "5479e6cbf91e98-19c73ab3076", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.7704000123189028, - "y": 1.0304060409681888, - "z": 1.8808962611859585 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.04100417598396379, - "y": 0.5074140771523286, - "z": 0.788305347568813 - } - }, - "material": { - "color": "#383838", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "c49d5cfe6dfb5-19c73ac01c0", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 0.22360500719770893, - "y": 1.0304060409681888, - "z": 1.8808962611859585 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.04100417598396379, - "y": 0.5074140771523286, - "z": 0.788305347568813 - } - }, - "material": { - "color": "#383838", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "8442f49ba1d1d8-19c73ac36a5", - "type": "box", - "name": "Box copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.27463536991175663, - "y": 1.0322191996408812, - "z": 1.5281304893510192 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.08575964842364789, - "y": 0.51, - "z": 0.9715037235609404 - } - }, - "material": { - "color": "#383838", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "16e41fc4aeeca-19c73ace5ed", - "type": "box", - "name": "Box copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -0.2715012704018097, - "y": 1.0322191996408812, - "z": 2.2554694411852387 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.04, - "y": 0.51, - "z": 0.9715037235609404 - } - }, - "material": { - "color": "#383838", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "e2d43dadde341-19c73af12a4", - "name": "Sink", - "children": [ - "faucet-base", - "faucet-neck", - "faucet-curve", - "faucet-head", - "faucet-handle", - "68b4bb7fb61378-19c73aa0261", - "5479e6cbf91e98-19c73ab3076", - "c49d5cfe6dfb5-19c73ac01c0", - "8442f49ba1d1d8-19c73ac36a5", - "16e41fc4aeeca-19c73ace5ed" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.59988395161818, - "y": 0.9535132879737271, - "z": 2.6793443991438437 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.7160886522166464, - "y": 0.40892918087790386, - "z": 0.8280100914128449 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspneen-ynu9", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.2733975025605969, - "y": 1.2202618787565602, - "z": 1.8803600531622169 - } - }, - { - "id": "80c4a613fb7268-19c73c9ac7a", - "title": "Dining chair", - "notes": "Highly detailed modern dining chair: solid oak frame with gently tapered legs, curved backrest with vertical slats, upholstered seat cushion in light beige fabric, subtle stitching and piping, realistic wood grain and fabric weave, slightly rounded edges, clean contemporary proportions", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "leg-rl", - "type": "cylinder", - "name": "Rear Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "leg-rr", - "type": "cylinder", - "name": "Rear Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "seat-frame", - "type": "box", - "name": "Seat Frame", - "dimensions": { - "width": 0.45, - "height": 0.04, - "depth": 0.45, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.42955268602766, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "seat-cushion", - "type": "box", - "name": "Seat Cushion", - "dimensions": { - "width": 0.46, - "height": 0.07, - "depth": 0.46, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.48, - "z": 0.02635144046412838 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.940132746868615 - } - }, - "material": { - "color": "#4d0a0a", - "softness": 0.92, - "metalness": 0, - "generateTexture": "beige linen fabric weave", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "back-rail-center", - "type": "box", - "name": "Backrest Rail Center", - "dimensions": { - "width": 0.2, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "back-rail-left", - "type": "box", - "name": "Backrest Rail Left", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "back-rail-right", - "type": "box", - "name": "Backrest Rail Right", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": -0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": -0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "slat-1", - "type": "box", - "name": "Back Slat 1", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "slat-2", - "type": "box", - "name": "Back Slat 2", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.61955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "slat-3", - "type": "box", - "name": "Back Slat 3", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "ed590d02961498-19c72f75860", - "name": "Group", - "children": [ - "leg-fl", - "leg-fr", - "leg-rl", - "leg-rr", - "seat-frame", - "back-rail-center", - "back-rail-left", - "back-rail-right", - "slat-1", - "slat-2", - "slat-3" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.0087908140402728, - "y": 0.5434133631727004, - "z": 4.26 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlr4uwtk-cm8u", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.42500000000000004, - "z": 0.00026035857325493184 - } - }, - { - "id": "9a67b23a28cf8-19c73cb7fe7", - "title": "Large ornate mahogany dining table", - "notes": "Large ornate mahogany dining table with carved legs and polished finish\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "table-top", - "type": "box", - "name": "Mahogany Table Top", - "dimensions": { - "width": 2.2, - "height": 0.06, - "depth": 1.1, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.72, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.1, - "metalness": 0, - "clearcoat": 0.9, - "generateTexture": "polished mahogany wood grain", - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-fl-block", - "type": "box", - "dimensions": { - "width": 0.1, - "height": 0.15, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.615, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "leg-fl-turned", - "type": "cylinder", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.03, - "height": 0.46 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.31, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Cylinder", - "cutouts": [] - }, - { - "id": "leg-fl-foot", - "type": "sphere", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.03999999999999998, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.8, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Sphere", - "cutouts": [] - }, - { - "id": "leg-fr-block", - "type": "box", - "dimensions": { - "width": 0.1, - "height": 0.15, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.615, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "leg-fr-turned", - "type": "cylinder", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.03, - "height": 0.46 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.31, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Cylinder", - "cutouts": [] - }, - { - "id": "leg-fr-foot", - "type": "sphere", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.03999999999999998, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.8, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Sphere", - "cutouts": [] - }, - { - "id": "leg-bl-block", - "type": "box", - "dimensions": { - "width": 0.1, - "height": 0.15, - "depth": 0.1 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.615, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "leg-bl-turned", - "type": "cylinder", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.03, - "height": 0.46 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.31, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Cylinder", - "cutouts": [] - }, - { - "id": "leg-bl-foot", - "type": "sphere", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.03999999999999998, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.8, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Sphere", - "cutouts": [] - }, - { - "id": "leg-br-block", - "type": "box", - "dimensions": { - "width": 0.1, - "height": 0.15, - "depth": 0.1 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.615, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "leg-br-turned", - "type": "cylinder", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.03, - "height": 0.46 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.31, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Cylinder", - "cutouts": [] - }, - { - "id": "leg-br-foot", - "type": "sphere", - "dimensions": { - "radius": 0.05 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.03999999999999998, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.8, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Sphere", - "cutouts": [] - }, - { - "id": "apron-long-1", - "type": "box", - "dimensions": { - "width": 1.8, - "height": 0.1, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.64, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "apron-long-2", - "type": "box", - "dimensions": { - "width": 1.8, - "height": 0.1, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.64, - "z": -0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "apron-short-1", - "type": "box", - "dimensions": { - "width": 0.04, - "height": 0.1, - "depth": 0.7 - }, - "transform": { - "position": { - "x": 0.95, - "y": 0.64, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - }, - { - "id": "apron-short-2", - "type": "box", - "dimensions": { - "width": 0.04, - "height": 0.1, - "depth": 0.7 - }, - "transform": { - "position": { - "x": -0.95, - "y": 0.64, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4d1a0f", - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "name": "Box", - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ornate-dining-table", - "name": "Ornate Mahogany Dining Table", - "children": [ - "table-top", - "leg-fl-block", - "leg-fl-turned", - "leg-fl-foot", - "leg-fr-block", - "leg-fr-turned", - "leg-fr-foot", - "leg-bl-block", - "leg-bl-turned", - "leg-bl-foot", - "leg-br-block", - "leg-br-turned", - "leg-br-foot", - "apron-long-1", - "apron-long-2", - "apron-short-1", - "apron-short-2" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.3670123249376638, - "y": 0.47636876395355976, - "z": 3.809280713021919 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuldg0-xqjz", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.37499999936670064, - "z": 0 - } - }, - { - "id": "b521e3d18de31-19c73cc4ab7", - "title": "Dining chair", - "notes": "Highly detailed modern dining chair: solid oak frame with gently tapered legs, curved backrest with vertical slats, upholstered seat cushion in light beige fabric, subtle stitching and piping, realistic wood grain and fabric weave, slightly rounded edges, clean contemporary proportions", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rl", - "type": "cylinder", - "name": "Rear Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rr", - "type": "cylinder", - "name": "Rear Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-frame", - "type": "box", - "name": "Seat Frame", - "dimensions": { - "width": 0.45, - "height": 0.04, - "depth": 0.45, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.42955268602766, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-cushion", - "type": "box", - "name": "Seat Cushion", - "dimensions": { - "width": 0.46, - "height": 0.07, - "depth": 0.46, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.48, - "z": 0.02635144046412838 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.940132746868615 - } - }, - "material": { - "color": "#4d0a0a", - "softness": 0.92, - "metalness": 0, - "generateTexture": "beige linen fabric weave", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-center", - "type": "box", - "name": "Backrest Rail Center", - "dimensions": { - "width": 0.2, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-left", - "type": "box", - "name": "Backrest Rail Left", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-right", - "type": "box", - "name": "Backrest Rail Right", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": -0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": -0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-1", - "type": "box", - "name": "Back Slat 1", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-2", - "type": "box", - "name": "Back Slat 2", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.61955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-3", - "type": "box", - "name": "Back Slat 3", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ed590d02961498-19c72f75860", - "name": "Group", - "children": [ - "leg-fl", - "leg-fr", - "leg-rl", - "leg-rr", - "seat-frame", - "back-rail-center", - "back-rail-left", - "back-rail-right", - "slat-1", - "slat-2", - "slat-3" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.021304087860674, - "y": 0.5331213412094118, - "z": 3.31 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlr4uwtk-cm8u", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.42455268602766005, - "z": 0.00026035857325493184 - } - }, - { - "id": "c4896d1ee8f58-19c73cca1c3", - "title": "Dining chair", - "notes": "Highly detailed modern dining chair: solid oak frame with gently tapered legs, curved backrest with vertical slats, upholstered seat cushion in light beige fabric, subtle stitching and piping, realistic wood grain and fabric weave, slightly rounded edges, clean contemporary proportions", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rl", - "type": "cylinder", - "name": "Rear Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rr", - "type": "cylinder", - "name": "Rear Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-frame", - "type": "box", - "name": "Seat Frame", - "dimensions": { - "width": 0.45, - "height": 0.04, - "depth": 0.45, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.42955268602766, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-cushion", - "type": "box", - "name": "Seat Cushion", - "dimensions": { - "width": 0.46, - "height": 0.07, - "depth": 0.46, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.48, - "z": 0.02635144046412838 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.940132746868615 - } - }, - "material": { - "color": "#4d0a0a", - "softness": 0.92, - "metalness": 0, - "generateTexture": "beige linen fabric weave", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-center", - "type": "box", - "name": "Backrest Rail Center", - "dimensions": { - "width": 0.2, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-left", - "type": "box", - "name": "Backrest Rail Left", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-right", - "type": "box", - "name": "Backrest Rail Right", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": -0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": -0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-1", - "type": "box", - "name": "Back Slat 1", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-2", - "type": "box", - "name": "Back Slat 2", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.61955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-3", - "type": "box", - "name": "Back Slat 3", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ed590d02961498-19c72f75860", - "name": "Group", - "children": [ - "leg-fl", - "leg-fr", - "leg-rl", - "leg-rr", - "seat-frame", - "back-rail-center", - "back-rail-left", - "back-rail-right", - "slat-1", - "slat-2", - "slat-3" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 0.0258311295758461, - "y": 0.5434518887030261, - "z": 4.257087223482772 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlr4uwtk-cm8u", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.42455268602766005, - "z": 0.00026035857325493184 - } - }, - { - "id": "5870c87d73e6e-19c73cd1041", - "title": "Dining chair", - "notes": "Highly detailed modern dining chair: solid oak frame with gently tapered legs, curved backrest with vertical slats, upholstered seat cushion in light beige fabric, subtle stitching and piping, realistic wood grain and fabric weave, slightly rounded edges, clean contemporary proportions", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-fr", - "type": "cylinder", - "name": "Front Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.015, - "height": 0.43 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.21455268602765998, - "z": 0.20000000000000007 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rl", - "type": "cylinder", - "name": "Rear Left Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-rr", - "type": "cylinder", - "name": "Rear Right Leg", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.42455268602766, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "generateTexture": "solid oak wood grain", - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-frame", - "type": "box", - "name": "Seat Frame", - "dimensions": { - "width": 0.45, - "height": 0.04, - "depth": 0.45, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.42955268602766, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "metalness": 0, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "seat-cushion", - "type": "box", - "name": "Seat Cushion", - "dimensions": { - "width": 0.46, - "height": 0.07, - "depth": 0.46, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.48, - "z": 0.02635144046412838 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 0.940132746868615 - } - }, - "material": { - "color": "#4d0a0a", - "softness": 0.92, - "metalness": 0, - "generateTexture": "beige linen fabric weave", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/4d5b048c925d.avif" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-center", - "type": "box", - "name": "Backrest Rail Center", - "dimensions": { - "width": 0.2, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.79955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-left", - "type": "box", - "name": "Backrest Rail Left", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "back-rail-right", - "type": "box", - "name": "Backrest Rail Right", - "dimensions": { - "width": 0.12, - "height": 0.08, - "depth": 0.03, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": -0.15, - "y": 0.79955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": -0.3, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-1", - "type": "box", - "name": "Back Slat 1", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-2", - "type": "box", - "name": "Back Slat 2", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.61955268602766, - "z": -0.22 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "slat-3", - "type": "box", - "name": "Back Slat 3", - "dimensions": { - "width": 0.03, - "height": 0.3, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.61955268602766, - "z": -0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#683427", - "softness": 0.95, - "roughness": 0.95, - "hardness": 0.05, - "fluffiness": 0.95, - "specularIntensity": 0.3, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.05, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ed590d02961498-19c72f75860", - "name": "Group", - "children": [ - "leg-fl", - "leg-fr", - "leg-rl", - "leg-rr", - "seat-frame", - "back-rail-center", - "back-rail-left", - "back-rail-right", - "slat-1", - "slat-2", - "slat-3" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 0.4759727625914219, - "y": 0.5487097275235999, - "z": 3.6356960320731617 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": true, - "bumpResponse": 1.25, - "bumpDamping": 0.86, - "libraryAssetId": "stg-mlr4uwtk-cm8u", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.42455268602766005, - "z": 0.00026035857325493184 - } - }, - { - "id": "86897b7437ec4-19c73d08c40", - "title": "kitchen wall cabinet", - "notes": "Matching wooden kitchen wall cabinet with frosted glass doors\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wall-cab-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.8, - "height": 0.9, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4a4a4a", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.76, - "height": 0.86, - "depth": 0.28 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-door-l", - "type": "box", - "name": "Left Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-l", - "type": "box", - "name": "Left Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-door-r", - "type": "box", - "name": "Right Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-r", - "type": "box", - "name": "Right Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "frosted-wall-cabinet", - "name": "Kitchen Wall Cabinet", - "children": [ - "wall-cab-carcass", - "wall-cab-door-l", - "wall-cab-glass-l", - "wall-cab-door-r", - "wall-cab-glass-r", - "wall-cab-handle-l", - "wall-cab-handle-r" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.7339840704817675, - "y": 2.1862344570288634, - "z": 4.379152567928243 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.2867158952939255, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspvl13-ymjy", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.44999999999999996, - "z": 0.01999999690800905 - } - }, - { - "id": "d4e7cf45e14168-19c73d2c59e", - "title": "kitchen wall cabinet", - "notes": "Matching wooden kitchen wall cabinet with frosted glass doors\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wall-cab-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.8, - "height": 0.9, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4a4a4a", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.76, - "height": 0.86, - "depth": 0.28 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-door-l", - "type": "box", - "name": "Left Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-l", - "type": "box", - "name": "Left Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-door-r", - "type": "box", - "name": "Right Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-r", - "type": "box", - "name": "Right Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "frosted-wall-cabinet", - "name": "Kitchen Wall Cabinet", - "children": [ - "wall-cab-carcass", - "wall-cab-door-l", - "wall-cab-glass-l", - "wall-cab-door-r", - "wall-cab-glass-r", - "wall-cab-handle-l", - "wall-cab-handle-r" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.734054470140882, - "y": 2.185881752908841, - "z": 3.4550831669585023 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspvl13-ymjy", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.44999999999999996, - "z": 0.01999999690800905 - } - }, - { - "id": "53ffa35fcc3b3-19c73d3c12b", - "title": "Wall-mounted stainless steel chimney range hood\n...", - "notes": "Wall-mounted stainless steel chimney range hood\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Designed with a wide base and vertical chimney using brushed stainless steel materials and integrated task lighting.", - "tags": [ - "staging-asset", - "kitchen", - "appliance" - ], - "primitives": [ - { - "id": "hood-base", - "type": "box", - "name": "Hood Base", - "dimensions": { - "width": 0.9, - "height": 0.1, - "depth": 0.5, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "softness": 0.12, - "metalness": 0.95, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-chimney", - "type": "box", - "name": "Chimney Flue", - "dimensions": { - "width": 0.3, - "height": 0.9, - "depth": 0.25 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": -0.125 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "softness": 0.12, - "metalness": 0.95, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-filter-l", - "type": "box", - "name": "Left Filter", - "dimensions": { - "width": 0.35, - "height": 0.01, - "depth": 0.4 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.0050000000000000044, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "softness": 0.12, - "metalness": 0.95, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-filter-r", - "type": "box", - "name": "Right Filter", - "dimensions": { - "width": 0.35, - "height": 0.01, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.0050000000000000044, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "softness": 0.12, - "metalness": 0.95, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-button-panel", - "type": "box", - "name": "Control Panel", - "dimensions": { - "width": 0.2, - "height": 0.04, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "softness": 0.12, - "metalness": 0.95, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-light-l", - "type": "sphere", - "name": "Left Light Lens", - "dimensions": { - "radius": 0.02 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.009999999999999995, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "emissive": "#FFFFFF", - "emissiveIntensity": 2, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0.95 - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "hood-light-r", - "type": "sphere", - "name": "Right Light Lens", - "dimensions": { - "radius": 0.02 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.009999999999999995, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#757575", - "emissive": "#FFFFFF", - "emissiveIntensity": 2, - "softness": 0.12, - "roughness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "metalness": 0.95 - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [ - { - "id": "hood-point-l", - "type": "point", - "color": "#FFF4E0", - "intensity": 2, - "position": { - "x": -0.3, - "y": -0.1, - "z": 0.15 - }, - "distance": 2, - "castShadow": true, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - }, - "_lightObj": { - "metadata": { - "version": 4.6, - "type": "Object", - "generator": "Object3D.toJSON" - }, - "object": { - "uuid": "c41dc894-9777-4c33-8605-393276ec27c2", - "type": "PointLight", - "name": "light:hood-point-l", - "castShadow": true, - "userData": { - "editorLightId": "hood-point-l", - "isEditorLight": true - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - -0.3, - -0.1, - 0.15, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "color": 16774368, - "intensity": 2, - "distance": 2, - "decay": 2, - "shadow": { - "bias": -0.003, - "camera": { - "uuid": "672dbf5b-bdb7-42f9-99d4-288d7180b848", - "type": "PerspectiveCamera", - "layers": 1, - "up": [ - 0, - 0, - -1 - ], - "fov": 90, - "zoom": 1, - "near": 0.5, - "far": 2, - "focus": 10, - "aspect": 1, - "filmGauge": 35, - "filmOffset": 0 - } - } - } - }, - "_helperObj": null, - "_proxyObj": { - "metadata": { - "version": 4.6, - "type": "Object", - "generator": "Object3D.toJSON" - }, - "geometries": [ - { - "uuid": "9549e411-da2c-47aa-9d08-5b6d1ea7ad51", - "type": "SphereGeometry", - "radius": 0.12, - "widthSegments": 12, - "heightSegments": 8, - "phiStart": 0, - "phiLength": 6.283185307179586, - "thetaStart": 0, - "thetaLength": 3.141592653589793 - }, - { - "uuid": "e5893b55-b057-4a77-829a-1801c1375aa7", - "type": "SphereGeometry", - "radius": 0.18, - "widthSegments": 12, - "heightSegments": 8, - "phiStart": 0, - "phiLength": 6.283185307179586, - "thetaStart": 0, - "thetaLength": 3.141592653589793 - }, - { - "uuid": "c2239c74-6791-4abb-82a1-16cde393b723", - "type": "BufferGeometry", - "data": { - "attributes": { - "position": { - "itemSize": 3, - "type": "Float32Array", - "array": [ - 0.15000000596046448, - 0, - 0, - 0.2800000011920929, - 0, - 0, - 0.07500000298023224, - 0, - 0.12990380823612213, - 0.14000000059604645, - 0, - 0.2424871176481247, - -0.07500000298023224, - 0, - 0.12990380823612213, - -0.14000000059604645, - 0, - 0.2424871176481247, - -0.15000000596046448, - 0, - 1.8369702788777518e-17, - -0.2800000011920929, - 0, - 3.429011054889572e-17, - -0.07500000298023224, - 0, - -0.12990380823612213, - -0.14000000059604645, - 0, - -0.2424871176481247, - 0.07500000298023224, - 0, - -0.12990380823612213, - 0.14000000059604645, - 0, - -0.2424871176481247 - ], - "normalized": false - } - }, - "boundingSphere": { - "center": [ - 0, - 0, - 0 - ], - "radius": 0.2800000042717651 - } - } - } - ], - "materials": [ - { - "uuid": "a7764e21-deaf-4e28-b0f2-817fe4a853e5", - "type": "MeshBasicMaterial", - "color": 16777215, - "envMapRotation": [ - 0, - 0, - 0, - "XYZ" - ], - "reflectivity": 1, - "refractionRatio": 0.98, - "opacity": 0.95, - "transparent": true, - "blendColor": 0 - }, - { - "uuid": "84c561e9-8d91-490c-90f0-3c2b2c74da50", - "type": "MeshBasicMaterial", - "color": 16774368, - "envMapRotation": [ - 0, - 0, - 0, - "XYZ" - ], - "reflectivity": 1, - "refractionRatio": 0.98, - "opacity": 0.35, - "transparent": true, - "blendColor": 0, - "depthWrite": false - }, - { - "uuid": "33dbc8c5-8ef9-4fd5-a11f-f97fb36d0fd0", - "type": "LineBasicMaterial", - "color": 16774368, - "opacity": 0.4, - "transparent": true, - "blendColor": 0 - }, - { - "uuid": "542bb025-0195-49ab-a4f5-911312080870", - "type": "SpriteMaterial", - "color": 16777215, - "map": "51104168-93ef-49fc-8398-089377183cd2", - "sizeAttenuation": true, - "opacity": 0.7, - "transparent": true, - "blendColor": 0, - "depthTest": false - } - ], - "textures": [ - { - "uuid": "51104168-93ef-49fc-8398-089377183cd2", - "name": "", - "image": "3bd5445d-0c20-4082-9175-6c3c8d286efb", - "mapping": 300, - "channel": 0, - "repeat": [ - 1, - 1 - ], - "offset": [ - 0, - 0 - ], - "center": [ - 0, - 0 - ], - "rotation": 0, - "wrap": [ - 1001, - 1001 - ], - "format": 1023, - "internalFormat": null, - "type": 1009, - "colorSpace": "", - "minFilter": 1008, - "magFilter": 1006, - "anisotropy": 1, - "flipY": true, - "generateMipmaps": true, - "premultiplyAlpha": false, - "unpackAlignment": 4 - } - ], - "images": [ - { - "uuid": "3bd5445d-0c20-4082-9175-6c3c8d286efb", - "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAAgCAYAAADaInAlAAAD9klEQVR4AeyZW4hNURjH97ikCA8u8SAPSpKiRB4oJKSUB1NIuZUkKZEHuRQeFEUhpFxKiczLpFwat1ITL0SSwgweXZoRwuD4/c9Z37Rm7z3nnGmKvc/e0/df3//71rfOXutb31ln7z19gvwv0xnICyDT2x8EeQHkBZDxDGR8+fkJkBdAxjOQ8eXnJ0BeABnPQEaXb8tO7AlQKBT6hGGTrqQZNxGsB8fBVjAdxK5VfsH/TNmC7/O5+gTziVcDi0+Sjk3K/54gyZzHHH6HgV/SQdMKFtDfRfBp41txPgOnwEZwCDwAbfQvRncK9gWM4nXgF+EBehPafJiFSdidgmMzhvVfxN5hdiVN7FFiEiWJLAAy1Bd0J/3oGAuuk9D56KLAJ0KeAvWhIjIYTyNxK9AmdUbQlovwtRvo88UfIy74/eW4XaNczD/tS9yEYlb/A99Zh/voDmByWoRN1TruwKVRgWL2Q2aDLeAzMDlD/FAzqtDjifeLJjzkNo4rHt7BTT5A/L5G7ESJJSxRkwpNpq2urm6twyz6VgGTUY4sQ48EkgLNBOJ3gXvgCPZo8BVIBtD09Cg+SRHE5orPbwb1Bj67CZg8Mr/TN6wjKTp2UUmZXDfzeO75+zu+yGmpWyT7tYgB+xv8MDCZYqSC/un69fPhj3fu9Ks0FEBfvn1DHBaS8kvA5Isj05yWuqsmBlc93xiPl6M6su3k2MQcRpQLTmNfGgpgOIltd7iGHg9MZIsPUuPwxumweuU5hni8HP1Dp54KUMV/nPnFJ1/qEJ5wGgogPGez9WhnN2f+jdc4CwjpCZ5t32rPFU/5+ThHz0sgmUMzGdSMpKEAvpNt3bQJB+HrwTg2ZgbQ8zhm8ESNg94hONpFLfGsFo9XQ+u9oDUeTz1NQwG0s9GbHbajT4MuN3nswk1gMpPf6qlmSGMPQ9tRDg2a1VQLrveYWPu5gdaOpKEAKmabDdLLmhde4EM2/QSoB/vwvwV6/EMFOlG2ifQQy4nX+wVU7UhNFIDbDr0V/OW41rUBfhnsBAOByUoKxp4ezFdRM0Y3ogcqBqYsQIlK4pTtt11z04sd6bJgg/Qt1xOCfxL4Yz5hzCVOpwW0KP5n645fTv/a5pM/YOxuyEdg4o+P83UZbwFJ0oksABLdBEz0Fq+qnDGgBehuX28FlzJoD1gNdNM4jD69LsYsCbZOA1RRdMRrk48VrVJT9JWiSy3u4cAkrn+ddaL13qI0MKFtIgugt7ki8e9BA9gLzoPwTWNvL5G68d1NuCYLoLvF5v5oBvICiOYkU568ADK13dHF5gUQzUmmPHkBZGq7o4vNCyCak0x58gLI1HZHF5sXQDQnNeWptJi/AAAA//9SkIC7AAAABklEQVQDAGffSFDFjUIgAAAAAElFTkSuQmCC" - } - ], - "object": { - "uuid": "b6086420-0f80-42cf-b80b-0573c846a748", - "type": "Group", - "name": "lightProxy:hood-point-l", - "userData": { - "editorLightId": "hood-point-l", - "isLightProxy": true - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - -0.3, - -0.1, - 0.15, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "children": [ - { - "uuid": "bdca6444-de21-4a10-b3a2-8171d8b3ebb5", - "type": "Mesh", - "userData": { - "editorLightId": "hood-point-l" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "9549e411-da2c-47aa-9d08-5b6d1ea7ad51", - "material": "a7764e21-deaf-4e28-b0f2-817fe4a853e5" - }, - { - "uuid": "f8113932-e3c8-4f53-89f8-3056f7a6e781", - "type": "Mesh", - "userData": { - "editorLightId": "hood-point-l" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "e5893b55-b057-4a77-829a-1801c1375aa7", - "material": "84c561e9-8d91-490c-90f0-3c2b2c74da50" - }, - { - "uuid": "30b36f18-7fae-4609-9a78-7ae2d02ad531", - "type": "LineSegments", - "userData": { - "editorLightId": "hood-point-l" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "c2239c74-6791-4abb-82a1-16cde393b723", - "material": "33dbc8c5-8ef9-4fd5-a11f-f97fb36d0fd0" - }, - { - "uuid": "2d0d0de5-353f-4210-9730-bcfe31f94058", - "type": "Sprite", - "userData": { - "editorLightId": "hood-point-l" - }, - "layers": 1, - "matrix": [ - 0.5, - 0, - 0, - 0, - 0, - 0.14, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0.28, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "material": "542bb025-0195-49ab-a4f5-911312080870" - } - ] - } - } - }, - { - "id": "hood-point-r", - "type": "point", - "color": "#FFF4E0", - "intensity": 2, - "position": { - "x": 0.3, - "y": -0.1, - "z": 0.15 - }, - "distance": 2, - "castShadow": false, - "name": "Point Light", - "target": { - "x": 0, - "y": 0, - "z": 0 - }, - "_lightObj": { - "metadata": { - "version": 4.6, - "type": "Object", - "generator": "Object3D.toJSON" - }, - "object": { - "uuid": "641a1327-fb6e-474d-80f3-56c77c6f0a31", - "type": "PointLight", - "name": "light:hood-point-r", - "userData": { - "editorLightId": "hood-point-r", - "isEditorLight": true - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0.3, - -0.1, - 0.15, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "color": 16774368, - "intensity": 2, - "distance": 2, - "decay": 2, - "shadow": { - "bias": -0.003, - "camera": { - "uuid": "2487e7f8-a5d1-46f7-98c8-3ec309059b50", - "type": "PerspectiveCamera", - "layers": 1, - "up": [ - 0, - 1, - 0 - ], - "fov": 90, - "zoom": 1, - "near": 0.5, - "far": 2, - "focus": 10, - "aspect": 1, - "filmGauge": 35, - "filmOffset": 0 - } - } - } - }, - "_helperObj": null, - "_proxyObj": { - "metadata": { - "version": 4.6, - "type": "Object", - "generator": "Object3D.toJSON" - }, - "geometries": [ - { - "uuid": "9549e411-da2c-47aa-9d08-5b6d1ea7ad51", - "type": "SphereGeometry", - "radius": 0.12, - "widthSegments": 12, - "heightSegments": 8, - "phiStart": 0, - "phiLength": 6.283185307179586, - "thetaStart": 0, - "thetaLength": 3.141592653589793 - }, - { - "uuid": "683acc21-0527-4728-9738-e72a475cba25", - "type": "SphereGeometry", - "radius": 0.18, - "widthSegments": 12, - "heightSegments": 8, - "phiStart": 0, - "phiLength": 6.283185307179586, - "thetaStart": 0, - "thetaLength": 3.141592653589793 - }, - { - "uuid": "c2239c74-6791-4abb-82a1-16cde393b723", - "type": "BufferGeometry", - "data": { - "attributes": { - "position": { - "itemSize": 3, - "type": "Float32Array", - "array": [ - 0.15000000596046448, - 0, - 0, - 0.2800000011920929, - 0, - 0, - 0.07500000298023224, - 0, - 0.12990380823612213, - 0.14000000059604645, - 0, - 0.2424871176481247, - -0.07500000298023224, - 0, - 0.12990380823612213, - -0.14000000059604645, - 0, - 0.2424871176481247, - -0.15000000596046448, - 0, - 1.8369702788777518e-17, - -0.2800000011920929, - 0, - 3.429011054889572e-17, - -0.07500000298023224, - 0, - -0.12990380823612213, - -0.14000000059604645, - 0, - -0.2424871176481247, - 0.07500000298023224, - 0, - -0.12990380823612213, - 0.14000000059604645, - 0, - -0.2424871176481247 - ], - "normalized": false - } - }, - "boundingSphere": { - "center": [ - 0, - 0, - 0 - ], - "radius": 0.2800000042717651 - } - } - } - ], - "materials": [ - { - "uuid": "ece17e4c-9a66-46cd-9bd3-3e33eba0c4cb", - "type": "MeshBasicMaterial", - "color": 16777215, - "envMapRotation": [ - 0, - 0, - 0, - "XYZ" - ], - "reflectivity": 1, - "refractionRatio": 0.98, - "opacity": 0.95, - "transparent": true, - "blendColor": 0 - }, - { - "uuid": "4cbc7b18-95fd-44d5-be51-2dfc663474ea", - "type": "MeshBasicMaterial", - "color": 16774368, - "envMapRotation": [ - 0, - 0, - 0, - "XYZ" - ], - "reflectivity": 1, - "refractionRatio": 0.98, - "opacity": 0.35, - "transparent": true, - "blendColor": 0, - "depthWrite": false - }, - { - "uuid": "4957196c-463f-4845-b619-49ced30e64da", - "type": "LineBasicMaterial", - "color": 16774368, - "opacity": 0.4, - "transparent": true, - "blendColor": 0 - }, - { - "uuid": "c928b1f1-5a12-4226-8276-d27e3db23d38", - "type": "SpriteMaterial", - "color": 16777215, - "map": "28e9828d-bd36-47a9-a6e9-7746d5623aca", - "sizeAttenuation": true, - "opacity": 0.7, - "transparent": true, - "blendColor": 0, - "depthTest": false - } - ], - "textures": [ - { - "uuid": "28e9828d-bd36-47a9-a6e9-7746d5623aca", - "name": "", - "image": "28c950ef-a7ef-4b71-93f1-41eef969607a", - "mapping": 300, - "channel": 0, - "repeat": [ - 1, - 1 - ], - "offset": [ - 0, - 0 - ], - "center": [ - 0, - 0 - ], - "rotation": 0, - "wrap": [ - 1001, - 1001 - ], - "format": 1023, - "internalFormat": null, - "type": 1009, - "colorSpace": "", - "minFilter": 1008, - "magFilter": 1006, - "anisotropy": 1, - "flipY": true, - "generateMipmaps": true, - "premultiplyAlpha": false, - "unpackAlignment": 4 - } - ], - "images": [ - { - "uuid": "28c950ef-a7ef-4b71-93f1-41eef969607a", - "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAAgCAYAAADaInAlAAAD9klEQVR4AeyZW4hNURjH97ikCA8u8SAPSpKiRB4oJKSUB1NIuZUkKZEHuRQeFEUhpFxKiczLpFwat1ITL0SSwgweXZoRwuD4/c9Z37Rm7z3nnGmKvc/e0/df3//71rfOXutb31ln7z19gvwv0xnICyDT2x8EeQHkBZDxDGR8+fkJkBdAxjOQ8eXnJ0BeABnPQEaXb8tO7AlQKBT6hGGTrqQZNxGsB8fBVjAdxK5VfsH/TNmC7/O5+gTziVcDi0+Sjk3K/54gyZzHHH6HgV/SQdMKFtDfRfBp41txPgOnwEZwCDwAbfQvRncK9gWM4nXgF+EBehPafJiFSdidgmMzhvVfxN5hdiVN7FFiEiWJLAAy1Bd0J/3oGAuuk9D56KLAJ0KeAvWhIjIYTyNxK9AmdUbQlovwtRvo88UfIy74/eW4XaNczD/tS9yEYlb/A99Zh/voDmByWoRN1TruwKVRgWL2Q2aDLeAzMDlD/FAzqtDjifeLJjzkNo4rHt7BTT5A/L5G7ESJJSxRkwpNpq2urm6twyz6VgGTUY4sQ48EkgLNBOJ3gXvgCPZo8BVIBtD09Cg+SRHE5orPbwb1Bj67CZg8Mr/TN6wjKTp2UUmZXDfzeO75+zu+yGmpWyT7tYgB+xv8MDCZYqSC/un69fPhj3fu9Ks0FEBfvn1DHBaS8kvA5Isj05yWuqsmBlc93xiPl6M6su3k2MQcRpQLTmNfGgpgOIltd7iGHg9MZIsPUuPwxumweuU5hni8HP1Dp54KUMV/nPnFJ1/qEJ5wGgogPGez9WhnN2f+jdc4CwjpCZ5t32rPFU/5+ThHz0sgmUMzGdSMpKEAvpNt3bQJB+HrwTg2ZgbQ8zhm8ESNg94hONpFLfGsFo9XQ+u9oDUeTz1NQwG0s9GbHbajT4MuN3nswk1gMpPf6qlmSGMPQ9tRDg2a1VQLrveYWPu5gdaOpKEAKmabDdLLmhde4EM2/QSoB/vwvwV6/EMFOlG2ifQQy4nX+wVU7UhNFIDbDr0V/OW41rUBfhnsBAOByUoKxp4ezFdRM0Y3ogcqBqYsQIlK4pTtt11z04sd6bJgg/Qt1xOCfxL4Yz5hzCVOpwW0KP5n645fTv/a5pM/YOxuyEdg4o+P83UZbwFJ0oksABLdBEz0Fq+qnDGgBehuX28FlzJoD1gNdNM4jD69LsYsCbZOA1RRdMRrk48VrVJT9JWiSy3u4cAkrn+ddaL13qI0MKFtIgugt7ki8e9BA9gLzoPwTWNvL5G68d1NuCYLoLvF5v5oBvICiOYkU568ADK13dHF5gUQzUmmPHkBZGq7o4vNCyCak0x58gLI1HZHF5sXQDQnNeWptJi/AAAA//9SkIC7AAAABklEQVQDAGffSFDFjUIgAAAAAElFTkSuQmCC" - } - ], - "object": { - "uuid": "15fb9704-4d00-4a87-94dd-eeab77f0fcd0", - "type": "Group", - "name": "lightProxy:hood-point-r", - "userData": { - "editorLightId": "hood-point-r", - "isLightProxy": true - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0.3, - -0.1, - 0.15, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "children": [ - { - "uuid": "4decb971-4f46-408e-911a-87334864cb79", - "type": "Mesh", - "userData": { - "editorLightId": "hood-point-r" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "9549e411-da2c-47aa-9d08-5b6d1ea7ad51", - "material": "ece17e4c-9a66-46cd-9bd3-3e33eba0c4cb" - }, - { - "uuid": "f5865869-9ea1-408d-bf99-016486e3158b", - "type": "Mesh", - "userData": { - "editorLightId": "hood-point-r" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "683acc21-0527-4728-9738-e72a475cba25", - "material": "4cbc7b18-95fd-44d5-be51-2dfc663474ea" - }, - { - "uuid": "cc6748e2-84f6-4b4a-a938-eeceaf0413c7", - "type": "LineSegments", - "userData": { - "editorLightId": "hood-point-r" - }, - "layers": 1, - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "geometry": "c2239c74-6791-4abb-82a1-16cde393b723", - "material": "4957196c-463f-4845-b619-49ced30e64da" - }, - { - "uuid": "d4a2edde-6ddd-4bef-af40-d7b8588c75ee", - "type": "Sprite", - "userData": { - "editorLightId": "hood-point-r" - }, - "layers": 1, - "matrix": [ - 0.5, - 0, - 0, - 0, - 0, - 0.14, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0.28, - 0, - 1 - ], - "up": [ - 0, - 1, - 0 - ], - "material": "c928b1f1-5a12-4226-8276-d27e3db23d38" - } - ] - } - } - } - ], - "groups": [ - { - "id": "stainless-chimney-hood", - "name": "Stainless Steel Chimney Range Hood", - "children": [ - "hood-base", - "hood-chimney", - "hood-filter-l", - "hood-filter-r", - "hood-button-panel", - "hood-light-l", - "hood-light-r" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.740618632346517, - "y": 2.4462029494015645, - "z": 0.3129874596416138 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1.3048132595239712, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspprgu-ben4", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.49499999426305297, - "z": 0.004999999888241291 - } - }, - { - "id": "16b55c06139a58-19c73d4f998", - "title": "kitchen wall cabinet", - "notes": "Matching wooden kitchen wall cabinet with frosted glass doors\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wall-cab-carcass", - "type": "box", - "name": "Cabinet Carcass", - "dimensions": { - "width": 0.8, - "height": 0.9, - "depth": 0.3 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4a4a4a", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.76, - "height": 0.86, - "depth": 0.28 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-door-l", - "type": "box", - "name": "Left Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-l", - "type": "box", - "name": "Left Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-door-r", - "type": "box", - "name": "Right Door Frame", - "dimensions": { - "width": 0.39, - "height": 0.88, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "softness": 0.6, - "metalness": 0, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-cab-glass-r", - "type": "box", - "name": "Right Frosted Glass", - "dimensions": { - "width": 0.28, - "height": 0.75, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.45, - "z": 0.16 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "opacity": 0.4, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.1, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-l", - "type": "cylinder", - "name": "Left Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wall-cab-handle-r", - "type": "cylinder", - "name": "Right Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.15 - }, - "transform": { - "position": { - "x": 0.05, - "y": 0.35, - "z": 0.18 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c4c4c4", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "frosted-wall-cabinet", - "name": "Kitchen Wall Cabinet", - "children": [ - "wall-cab-carcass", - "wall-cab-door-l", - "wall-cab-glass-l", - "wall-cab-door-r", - "wall-cab-glass-r", - "wall-cab-handle-l", - "wall-cab-handle-r" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.7404835482395695, - "y": 2.183006970153812, - "z": 1.6857432491479991 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.5347140343971224, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspvl13-ymjy", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.44999999999999996, - "z": 0.01999999690800905 - } - }, - { - "id": "2841a95215e2b-19c73d6a74f", - "title": "Two-slice chrome toaster with browning control d...", - "notes": "Two-slice chrome toaster with browning control dial\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "A polished chrome toaster with rounded edges, featuring two top slots, a side lever, and a front dial.", - "tags": [ - "staging-asset", - "kitchen-appliance" - ], - "primitives": [ - { - "id": "toaster-body", - "type": "box", - "name": "Toaster Body", - "dimensions": { - "width": 0.26, - "height": 0.18, - "depth": 0.16, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.1, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "softness": 0.05, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "id": "slot-1", - "type": "box", - "dimensions": { - "width": 0.14, - "height": 0.15, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": -0.035 - } - } - }, - { - "id": "slot-2", - "type": "box", - "dimensions": { - "width": 0.14, - "height": 0.15, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0.035 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "toaster-lever-track", - "type": "box", - "name": "Lever Track", - "dimensions": { - "width": 0.01, - "height": 0.1, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0.13, - "y": 0.11, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-lever-handle", - "type": "box", - "name": "Lever Handle", - "dimensions": { - "width": 0.04, - "height": 0.015, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.14, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.3, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-dial", - "type": "cylinder", - "name": "Browning Dial", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.07, - "z": 0.085 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.4, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-foot-1", - "type": "cylinder", - "name": "Foot", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0.09, - "y": 0.005, - "z": 0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-foot-2", - "type": "cylinder", - "name": "Foot", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.01 - }, - "transform": { - "position": { - "x": -0.09, - "y": 0.005, - "z": 0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-foot-3", - "type": "cylinder", - "name": "Foot", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0.09, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toaster-foot-4", - "type": "cylinder", - "name": "Foot", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.01 - }, - "transform": { - "position": { - "x": -0.09, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "chrome-toaster", - "name": "Chrome Toaster", - "children": [ - "toaster-body", - "toaster-lever-track", - "toaster-lever-handle", - "toaster-dial", - "toaster-foot-1", - "toaster-foot-2", - "toaster-foot-3", - "toaster-foot-4" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.53024050161023, - "y": 1.0784581482665967, - "z": 1.5159435448934113 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspqdb5-6c69", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.02000000216066837, - "y": 0.0950000018440187, - "z": 0.006250037542275558 - } - }, - { - "id": "d146e81adcfb6-19c73d6f1d1", - "title": "Automatic drip coffee maker with glass carafe\n\nI...", - "notes": "Automatic drip coffee maker with glass carafe\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Designed with a sleek black plastic body, stainless accents, and a glass carafe using high transmission and IOR.", - "tags": [ - "staging-asset", - "kitchen-appliance" - ], - "primitives": [ - { - "id": "coffee-base", - "type": "box", - "name": "Base Plate", - "dimensions": { - "width": 0.2, - "height": 0.03, - "depth": 0.25, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.015, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.3, - "metalness": 0.2, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-warming-plate", - "type": "cylinder", - "name": "Warming Plate", - "dimensions": { - "radiusTop": 0.07, - "radiusBottom": 0.07, - "height": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.032, - "z": 0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.1, - "metalness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-back-pillar", - "type": "box", - "name": "Water Reservoir Housing", - "dimensions": { - "width": 0.18, - "height": 0.32, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.19, - "z": -0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.3, - "metalness": 0.2, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-top-head", - "type": "box", - "name": "Brew Head", - "dimensions": { - "width": 0.18, - "height": 0.06, - "depth": 0.22, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.32, - "z": -0.015 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.3, - "metalness": 0.2, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-carafe-glass", - "type": "cylinder", - "name": "Glass Carafe", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.075, - "height": 0.16 - }, - "transform": { - "position": { - "x": 0, - "y": 0.115, - "z": 0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "opacity": 1, - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.01, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-liquid", - "type": "cylinder", - "name": "Coffee Liquid", - "dimensions": { - "radiusTop": 0.07, - "radiusBottom": 0.07, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.075, - "z": 0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C1B0E", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-carafe-handle", - "type": "box", - "name": "Carafe Handle", - "dimensions": { - "width": 0.02, - "height": 0.12, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0.14, - "z": 0.13 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-display", - "type": "box", - "name": "Digital Display", - "dimensions": { - "width": 0.06, - "height": 0.03, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.25, - "z": -0.024 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "emissive": "#00FF00", - "emissiveIntensity": 0.5, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "coffee-accent-strip", - "type": "box", - "name": "Chrome Accent", - "dimensions": { - "width": 0.182, - "height": 0.01, - "depth": 0.102 - }, - "transform": { - "position": { - "x": 0, - "y": 0.29, - "z": -0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "coffee-maker-group", - "name": "Automatic Drip Coffee Maker", - "children": [ - "coffee-base", - "coffee-warming-plate", - "coffee-back-pillar", - "coffee-top-head", - "coffee-carafe-glass", - "coffee-liquid", - "coffee-carafe-handle", - "coffee-display", - "coffee-accent-strip" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.528265138870446, - "y": 1.1498788004517702, - "z": 4.666337898166567 - }, - "rotation": { - "x": -3.141592653589793, - "y": 1.2353040676263245, - "z": -3.141592653589793 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspqto9-s46y", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.17499999983236195, - "z": 0.017000000141561028 - } - }, - { - "id": "b9d431e7a8db-19c73d7935a", - "title": "Modern cordless electric kettle with brushed met...", - "notes": "Modern cordless electric kettle with brushed metal finish\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "kitchen-appliance" - ], - "primitives": [ - { - "id": "kettle-power-base", - "type": "cylinder", - "name": "Power Base", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1a1a1a", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-main-vessel", - "type": "cylinder", - "name": "Kettle Body", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.07, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0, - "y": 0.12, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#d1d1d1", - "softness": 0.2, - "metalness": 0.9, - "generateTexture": "brushed stainless steel finish", - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(37%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(71%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-lid", - "type": "cylinder", - "name": "Kettle Lid", - "dimensions": { - "radiusTop": 0.06, - "radiusBottom": 0.06, - "height": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#d1d1d1", - "softness": 0.2, - "metalness": 0.9, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-lid-knob", - "type": "cylinder", - "name": "Lid Knob", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.24, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1a1a1a", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-handle", - "type": "box", - "name": "Handle", - "dimensions": { - "width": 0.025, - "height": 0.16, - "depth": 0.04, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.13, - "z": -0.09 - }, - "rotation": { - "x": -0.1, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1a1a1a", - "softness": 0.8, - "metalness": 0.1, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-spout", - "type": "cone", - "name": "Spout", - "dimensions": { - "radius": 0.02, - "height": 0.07 - }, - "transform": { - "position": { - "x": 0, - "y": 0.18, - "z": 0.07 - }, - "rotation": { - "x": 0.8, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#d1d1d1", - "softness": 0.2, - "metalness": 0.9, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "kettle-switch", - "type": "box", - "name": "Power Switch", - "dimensions": { - "width": 0.015, - "height": 0.03, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": -0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "emissive": "#ff0000", - "emissiveIntensity": 0.2, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "modern-electric-kettle", - "name": "Modern Electric Kettle", - "children": [ - "kettle-power-base", - "kettle-main-vessel", - "kettle-lid", - "kettle-lid-knob", - "kettle-handle", - "kettle-spout", - "kettle-switch" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.737252095959948, - "y": 1.1082720912950728, - "z": 0.3542968078051474 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspt8rs-h6kg", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.125, - "z": -0.004422579425060177 - } - }, - { - "id": "d27de72b5e0e88-19c73d801ac", - "title": "High-speed kitchen blender with glass jar and me...", - "notes": "High-speed kitchen blender with glass jar and metal base\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "kitchen-appliance" - ], - "primitives": [ - { - "id": "blender-base", - "type": "box", - "name": "Motor Base", - "dimensions": { - "width": 0.18, - "height": 0.15, - "depth": 0.18, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.075, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.9, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(190%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(224%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "blender-dial", - "type": "cylinder", - "name": "Control Knob", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.08, - "z": 0.09 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "metalness": 0.2, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "blender-jar", - "type": "cylinder", - "name": "Glass Jar", - "dimensions": { - "radiusTop": 0.07, - "radiusBottom": 0.05, - "height": 0.25 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "opacity": 0.1, - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.01, - "softness": 1, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "blender-handle", - "type": "torus", - "name": "Jar Handle", - "dimensions": { - "radius": 0.06, - "tube": 0.012 - }, - "transform": { - "position": { - "x": 0.07, - "y": 0.28, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1.2, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "opacity": 0.1, - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.01, - "softness": 1, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "blender-lid", - "type": "cylinder", - "name": "Lid", - "dimensions": { - "radiusTop": 0.075, - "radiusBottom": 0.075, - "height": 0.025 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4125, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.8, - "metalness": 0, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "kitchen-blender", - "name": "High-Speed Blender", - "children": [ - "blender-base", - "blender-dial", - "blender-jar", - "blender-handle", - "blender-lid" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.598440540578837, - "y": 1.1821618341549747, - "z": 0.39175803651442287 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspryf1-j4qa", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.03320012858728233, - "y": 0.21249999860301613, - "z": 0.003750044043080797 - } - }, - { - "id": "e9ad475608341-19c73d95aaf", - "title": "Art deco gold-finished bar cart with two glass s...", - "notes": "Art deco gold-finished bar cart with two glass shelves and wheels\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Art Deco bar cart with a gold-finished frame, two glass shelves, and functional caster wheels.", - "tags": [ - "staging-asset", - "bar-cart", - "art-deco", - "furniture" - ], - "primitives": [ - { - "id": "bc-leg-fl", - "type": "cylinder", - "name": "Leg Front Left", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.45, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-leg-fr", - "type": "cylinder", - "name": "Leg Front Right", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.45, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-leg-bl", - "type": "cylinder", - "name": "Leg Back Left", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.45, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-leg-br", - "type": "cylinder", - "name": "Leg Back Right", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.45, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 0.9, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-shelf-bottom", - "type": "box", - "name": "Glass Shelf Bottom", - "dimensions": { - "width": 0.65, - "height": 0.01, - "depth": 0.45 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.01, - "opacity": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-shelf-top", - "type": "box", - "name": "Glass Shelf Top", - "dimensions": { - "width": 0.65, - "height": 0.01, - "depth": 0.45 - }, - "transform": { - "position": { - "x": 0, - "y": 0.7, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.01, - "opacity": 1, - "softness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-handle", - "type": "cylinder", - "name": "Handle", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.45 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.85, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-wheel-fl", - "type": "sphere", - "name": "Wheel FL", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.04, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.8, - "metalness": 0.2, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-wheel-fr", - "type": "sphere", - "name": "Wheel FR", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.04, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.8, - "metalness": 0.2, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-wheel-bl", - "type": "sphere", - "name": "Wheel BL", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.04, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.8, - "metalness": 0.2, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc-wheel-br", - "type": "sphere", - "name": "Wheel BR", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.04, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.8, - "metalness": 0.2, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "art-deco-bar-cart", - "name": "Art Deco Bar Cart", - "children": [ - "bc-leg-fl", - "bc-leg-fr", - "bc-leg-bl", - "bc-leg-br", - "bc-shelf-bottom", - "bc-shelf-top", - "bc-handle", - "bc-wheel-fl", - "bc-wheel-fr", - "bc-wheel-bl", - "bc-wheel-br" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.1617934539333223, - "y": 0.5389120073767927, - "z": 0.3863584703065891 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuq1dl-ksdt", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.43250041351490937, - "z": 0 - } - }, - { - "id": "2366af35eadcf8-19c73da0dcc", - "title": "Detailed porcelain dinner plate with gold rim an...", - "notes": "Detailed porcelain dinner plate with gold rim and floral pattern\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Fine porcelain plate with a gold-leaf torus rim and a central floral texture for high-end dining realism.", - "tags": [ - "staging-asset", - "tableware" - ], - "primitives": [ - { - "id": "plate-body", - "type": "cylinder", - "name": "Porcelain Body", - "dimensions": { - "radiusTop": 0.13, - "radiusBottom": 0.09, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "metalness": 0, - "ior": 1.5, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.115, - "radiusBottom": 0.1, - "height": 0.016 - }, - "transform": { - "position": { - "x": 0, - "y": 0.003, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "plate-gold-rim", - "type": "torus", - "name": "Gold Rim", - "dimensions": { - "radius": 0.128, - "tube": 0.002 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plate-floral-pattern", - "type": "cylinder", - "name": "Floral Pattern Surface", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.1, - "height": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "textureHardness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(207%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(241%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "dinner-plate-set", - "name": "Porcelain Dinner Plate", - "children": [ - "plate-body", - "plate-gold-rim", - "plate-floral-pattern" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.2032666946891117, - "y": 0.35538681201078215, - "z": 0.457809393418966 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuvghf-f295", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.015000253395232687, - "z": 0 - } - }, - { - "id": "9614072b45308-19c73da284e", - "title": "Detailed porcelain dinner plate with gold rim an...", - "notes": "Detailed porcelain dinner plate with gold rim and floral pattern\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Fine porcelain plate with a gold-leaf torus rim and a central floral texture for high-end dining realism.", - "tags": [ - "staging-asset", - "tableware" - ], - "primitives": [ - { - "id": "plate-body", - "type": "cylinder", - "name": "Porcelain Body", - "dimensions": { - "radiusTop": 0.13, - "radiusBottom": 0.09, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "metalness": 0, - "ior": 1.5, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.115, - "radiusBottom": 0.1, - "height": 0.016 - }, - "transform": { - "position": { - "x": 0, - "y": 0.003, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "plate-gold-rim", - "type": "torus", - "name": "Gold Rim", - "dimensions": { - "radius": 0.128, - "tube": 0.002 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plate-floral-pattern", - "type": "cylinder", - "name": "Floral Pattern Surface", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.1, - "height": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "textureHardness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(207%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(241%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "dinner-plate-set", - "name": "Porcelain Dinner Plate", - "children": [ - "plate-body", - "plate-gold-rim", - "plate-floral-pattern" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.1965681617029897, - "y": 0.42327413092342614, - "z": 0.5170610864361298 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuvghf-f295", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.015000253395232687, - "z": 0 - } - }, - { - "id": "b56a8224e0b41-19c73da6a7c", - "title": "Detailed porcelain dinner plate with gold rim an...", - "notes": "Detailed porcelain dinner plate with gold rim and floral pattern\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Fine porcelain plate with a gold-leaf torus rim and a central floral texture for high-end dining realism.", - "tags": [ - "staging-asset", - "tableware" - ], - "primitives": [ - { - "id": "plate-body", - "type": "cylinder", - "name": "Porcelain Body", - "dimensions": { - "radiusTop": 0.13, - "radiusBottom": 0.09, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "metalness": 0, - "ior": 1.5, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.115, - "radiusBottom": 0.1, - "height": 0.016 - }, - "transform": { - "position": { - "x": 0, - "y": 0.003, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "plate-gold-rim", - "type": "torus", - "name": "Gold Rim", - "dimensions": { - "radius": 0.128, - "tube": 0.002 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plate-floral-pattern", - "type": "cylinder", - "name": "Floral Pattern Surface", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.1, - "height": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "textureHardness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(207%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(241%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "dinner-plate-set", - "name": "Porcelain Dinner Plate", - "children": [ - "plate-body", - "plate-gold-rim", - "plate-floral-pattern" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.2651117557232083, - "y": 0.8539772864259029, - "z": 0.40488177152786675 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuvghf-f295", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.015000253395232687, - "z": 0 - } - }, - { - "id": "35b2a660f6f678-19c73db81a7", - "title": "Detailed porcelain dinner plate with gold rim an...", - "notes": "Detailed porcelain dinner plate with gold rim and floral pattern\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Fine porcelain plate with a gold-leaf torus rim and a central floral texture for high-end dining realism.", - "tags": [ - "staging-asset", - "tableware" - ], - "primitives": [ - { - "id": "plate-body", - "type": "cylinder", - "name": "Porcelain Body", - "dimensions": { - "radiusTop": 0.13, - "radiusBottom": 0.09, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "metalness": 0, - "ior": 1.5, - "thickness": 0.01, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.115, - "radiusBottom": 0.1, - "height": 0.016 - }, - "transform": { - "position": { - "x": 0, - "y": 0.003, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "plate-gold-rim", - "type": "torus", - "name": "Gold Rim", - "dimensions": { - "radius": 0.128, - "tube": 0.002 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plate-floral-pattern", - "type": "cylinder", - "name": "Floral Pattern Surface", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.1, - "height": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.1, - "textureHardness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(207%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(241%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "dinner-plate-set", - "name": "Porcelain Dinner Plate", - "children": [ - "plate-body", - "plate-gold-rim", - "plate-floral-pattern" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.264493009384819, - "y": 0.8980005677819676, - "z": 0.4371962543984047 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsuvghf-f295", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.015000253395232687, - "z": 0 - } - }, - { - "id": "995aeff211eac-19c73dc9250", - "title": "Wine glass", - "notes": "An elegant clear crystal wine glass with a thin stem\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "empty", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wine-glass-base", - "type": "cylinder", - "name": "Glass Base", - "dimensions": { - "radiusTop": 0.035, - "radiusBottom": 0.035, - "height": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0025, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wine-glass-stem", - "type": "cylinder", - "name": "Glass Stem", - "dimensions": { - "radiusTop": 0.002, - "radiusBottom": 0.004, - "height": 0.12 - }, - "transform": { - "position": { - "x": 0, - "y": 0.065, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wine-glass-bowl", - "type": "cylinder", - "name": "Glass Bowl", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.038, - "radiusBottom": 0.018, - "height": 0.098 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "wine-glass-group", - "name": "Wine Glass", - "children": [ - "wine-glass-base", - "wine-glass-stem", - "wine-glass-bowl" - ] - } - ] - }, - "interactions": [ - { - "id": "cycle-state-default-to-state-mlrogc5m-n4t3", - "label": "to full", - "to": "state-mlrogc5m-n4t3" - } - ] - }, - { - "id": "state-mlrogc5m-n4t3", - "name": "full", - "scene": { - "tags": [], - "primitives": [ - { - "id": "wine-glass-base", - "type": "cylinder", - "name": "Glass Base", - "dimensions": { - "radiusTop": 0.035, - "radiusBottom": 0.035, - "height": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0025, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wine-glass-stem", - "type": "cylinder", - "name": "Glass Stem", - "dimensions": { - "radiusTop": 0.002, - "radiusBottom": 0.004, - "height": 0.12 - }, - "transform": { - "position": { - "x": 0, - "y": 0.065, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wine-glass-bowl", - "type": "cylinder", - "name": "Glass Bowl", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 1, - "ior": 1.5, - "thickness": 0.005, - "opacity": 0.3, - "metalness": 0.1, - "softness": 0, - "specularIntensity": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.038, - "radiusBottom": 0.018, - "height": 0.098 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "4be2d15c9b5c28-19c6f847dba", - "type": "cylinder", - "name": "Glass Bowl copy", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.02, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0.0008642174653056001, - "y": 0.175, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.8322632763025389, - "y": 0.7400655060601062, - "z": 0.8322632763025389 - } - }, - "material": { - "color": "#230101", - "transmission": 0, - "ior": 1, - "thickness": 0, - "opacity": 0.39, - "metalness": 0, - "softness": 0.02, - "specularIntensity": 0, - "roughness": 0.02, - "hardness": 0, - "fluffiness": 0, - "specularColor": "#700000", - "envMapIntensity": 1.6, - "textureDataUrl": null, - "attenuationColor": "#620404", - "attenuationDistance": 0.01, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.03, - "textureSoftness": 0.08, - "textureHardness": 0.7, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.038, - "radiusBottom": 0.018, - "height": 0.098 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "87ed79bda3db78-19c6f877ae9", - "name": "Glass", - "children": [ - "wine-glass-base", - "wine-glass-stem", - "wine-glass-bowl", - "4be2d15c9b5c28-19c6f847dba" - ], - "pickable": false - } - ] - }, - "interactions": [ - { - "id": "cycle-state-mlrogc5m-n4t3-to-state-default", - "label": "to empty", - "to": "state-default" - } - ] - } - ], - "currentStateId": "state-mlrogc5m-n4t3", - "actions": [ - { - "id": "cycle-state-default-to-state-mlrogc5m-n4t3", - "label": "to full", - "from": "state-default", - "to": "state-mlrogc5m-n4t3" - }, - { - "id": "cycle-state-mlrogc5m-n4t3-to-state-default", - "label": "to empty", - "from": "state-mlrogc5m-n4t3", - "to": "state-default" - } - ], - "transform": { - "position": { - "x": 1.0177538809809166, - "y": 0.9429782021892605, - "z": 0.4498776100590399 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkty6n-sc6b", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.11125000042840838, - "z": 0 - } - }, - { - "id": "0695d8eaecdef-19c73dd202d", - "title": "Salt and pepper Shakers", - "notes": "A matching pair of glass and chrome salt and pepper shakers\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "salt-glass", - "type": "cylinder", - "name": "Salt Glass Body", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.06 - }, - "transform": { - "position": { - "x": -0.025, - "y": 0.03, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.01, - "opacity": 1, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "salt-fill", - "type": "cylinder", - "name": "Salt Grains", - "dimensions": { - "radiusTop": 0.018, - "radiusBottom": 0.018, - "height": 0.04 - }, - "transform": { - "position": { - "x": -0.025, - "y": 0.021, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 1, - "generateTexture": "fine white salt grains", - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(121%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(155%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "salt-cap", - "type": "cylinder", - "name": "Salt Chrome Cap", - "dimensions": { - "radiusTop": 0.021, - "radiusBottom": 0.021, - "height": 0.01 - }, - "transform": { - "position": { - "x": -0.025, - "y": 0.065, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 0, - "softness": 0.02, - "envMapIntensity": 1.5, - "roughness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pepper-glass", - "type": "cylinder", - "name": "Pepper Glass Body", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.06 - }, - "transform": { - "position": { - "x": 0.025, - "y": 0.03, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.01, - "opacity": 1, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pepper-fill", - "type": "cylinder", - "name": "Pepper Grains", - "dimensions": { - "radiusTop": 0.018, - "radiusBottom": 0.018, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.025, - "y": 0.021, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 1, - "generateTexture": "cracked black pepper corns", - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(147%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(181%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pepper-cap", - "type": "cylinder", - "name": "Pepper Chrome Cap", - "dimensions": { - "radiusTop": 0.021, - "radiusBottom": 0.021, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0.025, - "y": 0.065, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 0, - "softness": 0.02, - "envMapIntensity": 1.5, - "roughness": 0.02, - "hardness": 0.92, - "fluffiness": 0, - "specularIntensity": 1.25, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 0.98, - "transmission": 1, - "ior": 1.52, - "thickness": 0.35, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.9, - "clearcoatRoughness": 0.02, - "textureSoftness": 0.1, - "textureHardness": 0.85, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "shaker-set", - "name": "Salt and Pepper Shakers", - "children": [ - "salt-glass", - "salt-fill", - "salt-cap", - "pepper-glass", - "pepper-fill", - "pepper-cap" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -3.9803931457097406, - "y": 1.0048145678774343, - "z": 0.5215084811167934 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkuqqq-pk0o", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.03500000027939677, - "z": 0 - } - }, - { - "id": "3501961e59bfd-19c73dd7348", - "title": "Woven rattan placemat", - "notes": "A circular woven rattan placemat with natural fiber texture\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "rattan-placemat-base", - "type": "cylinder", - "name": "Woven Placemat", - "dimensions": { - "radiusTop": 0.19, - "radiusBottom": 0.19, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.40676374356370754, - "z": 1 - } - }, - "material": { - "color": "#692121", - "softness": 0.88, - "metalness": 0, - "generateTexture": "circular woven rattan pattern, natural straw fiber texture", - "textureHardness": 0.7, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(257%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(291%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "3dce77e7665ee8-19c6f8ae317", - "type": "torus", - "name": "Torus", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.49, - "tube": 0.01, - "radialSegments": 3, - "tubularSegments": 61, - "arcDeg": 360 - }, - "transform": { - "position": { - "x": -7.890569033608408e-05, - "y": 0.007115075617281391, - "z": 0 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.3, - "y": 0.3, - "z": 0.2593712678992066 - } - }, - "material": { - "color": "#4e4404", - "roughness": 0.25, - "softness": 0.25, - "hardness": 0.55, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "alphaCutoff": 0, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "67866e86df5288-19c6f8d3862", - "name": "Placemat", - "children": [ - "rattan-placemat-base", - "3dce77e7665ee8-19c6f8ae317" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.6918154969521217, - "y": 0.8540616060927329, - "z": 3.2486867503780283 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkvu6p-gge7", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.006163739011521584, - "z": 0 - } - }, - { - "id": "106836e6061308-19c73ddca05", - "title": "Woven rattan placemat", - "notes": "A circular woven rattan placemat with natural fiber texture\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "rattan-placemat-base", - "type": "cylinder", - "name": "Woven Placemat", - "dimensions": { - "radiusTop": 0.19, - "radiusBottom": 0.19, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.40676374356370754, - "z": 1 - } - }, - "material": { - "color": "#692121", - "softness": 0.88, - "metalness": 0, - "generateTexture": "circular woven rattan pattern, natural straw fiber texture", - "textureHardness": 0.7, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(257%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(291%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "3dce77e7665ee8-19c6f8ae317", - "type": "torus", - "name": "Torus", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.49, - "tube": 0.01, - "radialSegments": 3, - "tubularSegments": 61, - "arcDeg": 360 - }, - "transform": { - "position": { - "x": -7.890569033608408e-05, - "y": 0.007115075617281391, - "z": 0 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.3, - "y": 0.3, - "z": 0.2593712678992066 - } - }, - "material": { - "color": "#4e4404", - "roughness": 0.25, - "softness": 0.25, - "hardness": 0.55, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "alphaCutoff": 0, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "67866e86df5288-19c6f8d3862", - "name": "Placemat", - "children": [ - "rattan-placemat-base", - "3dce77e7665ee8-19c6f8ae317" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.03199168640492356, - "y": 0.8639676887097529, - "z": 3.2080873974631094 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkvu6p-gge7", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.006163739011521584, - "z": 0 - } - }, - { - "id": "a4d9f0c23dd5b-19c73ddea29", - "title": "Woven rattan placemat", - "notes": "A circular woven rattan placemat with natural fiber texture\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "rattan-placemat-base", - "type": "cylinder", - "name": "Woven Placemat", - "dimensions": { - "radiusTop": 0.19, - "radiusBottom": 0.19, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.40676374356370754, - "z": 1 - } - }, - "material": { - "color": "#692121", - "softness": 0.88, - "metalness": 0, - "generateTexture": "circular woven rattan pattern, natural straw fiber texture", - "textureHardness": 0.7, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(257%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(291%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "3dce77e7665ee8-19c6f8ae317", - "type": "torus", - "name": "Torus", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.49, - "tube": 0.01, - "radialSegments": 3, - "tubularSegments": 61, - "arcDeg": 360 - }, - "transform": { - "position": { - "x": -7.890569033608408e-05, - "y": 0.007115075617281391, - "z": 0 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.3, - "y": 0.3, - "z": 0.2593712678992066 - } - }, - "material": { - "color": "#4e4404", - "roughness": 0.25, - "softness": 0.25, - "hardness": 0.55, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "alphaCutoff": 0, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "67866e86df5288-19c6f8d3862", - "name": "Placemat", - "children": [ - "rattan-placemat-base", - "3dce77e7665ee8-19c6f8ae317" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.6857401667092435, - "y": 0.859627743601119, - "z": 4.289689745315966 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkvu6p-gge7", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.006163739011521584, - "z": 0 - } - }, - { - "id": "86bf2f5237ec6-19c73de0fe1", - "title": "Woven rattan placemat", - "notes": "A circular woven rattan placemat with natural fiber texture\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "rattan-placemat-base", - "type": "cylinder", - "name": "Woven Placemat", - "dimensions": { - "radiusTop": 0.19, - "radiusBottom": 0.19, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 0.40676374356370754, - "z": 1 - } - }, - "material": { - "color": "#692121", - "softness": 0.88, - "metalness": 0, - "generateTexture": "circular woven rattan pattern, natural straw fiber texture", - "textureHardness": 0.7, - "roughness": 0.88, - "hardness": 0.1, - "fluffiness": 0.45, - "specularIntensity": 0.35, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(257%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(291%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.9, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "3dce77e7665ee8-19c6f8ae317", - "type": "torus", - "name": "Torus", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.49, - "tube": 0.01, - "radialSegments": 3, - "tubularSegments": 61, - "arcDeg": 360 - }, - "transform": { - "position": { - "x": -7.890569033608408e-05, - "y": 0.007115075617281391, - "z": 0 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.3, - "y": 0.3, - "z": 0.2593712678992066 - } - }, - "material": { - "color": "#4e4404", - "roughness": 0.25, - "softness": 0.25, - "hardness": 0.55, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.65, - "clearcoatRoughness": 0.08, - "alphaCutoff": 0, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "67866e86df5288-19c6f8d3862", - "name": "Placemat", - "children": [ - "rattan-placemat-base", - "3dce77e7665ee8-19c6f8ae317" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.04934858727521346, - "y": 0.8540464700703785, - "z": 4.288064914106616 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkvu6p-gge7", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.006163739011521584, - "z": 0 - } - }, - { - "id": "bd9f45e625fdc-19c73de49ea", - "title": "Stainless steel cutlery set", - "notes": "A set of polished stainless steel fork, knife, and spoon\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fork-handle", - "type": "box", - "name": "Fork Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.12, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003, - "z": -0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fork-head", - "type": "box", - "name": "Fork Head", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003564758820788494, - "z": 0.02485763540666619 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1703822352869635, - "y": 1, - "z": 0.17389708086299438 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "knife-handle", - "type": "box", - "name": "Knife Handle", - "dimensions": { - "width": 0.012, - "height": 0.01, - "depth": 0.1, - "edgeRadius": 0.003 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "knife-blade", - "type": "box", - "name": "Knife Blade", - "dimensions": { - "width": 0.018, - "height": 0.002, - "depth": 0.13, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.001, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-handle", - "type": "box", - "name": "Spoon Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.14, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.003, - "z": -0.03 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-bowl", - "type": "sphere", - "name": "Spoon Bowl", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.0045, - "z": 0.06 - }, - "scale": { - "x": 1.2, - "y": 0.3, - "z": 1.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "sphere", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "scale": { - "x": 0.9, - "y": 1, - "z": 0.9 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "87f20df44226d-19c6f807e4e", - "type": "box", - "name": "Fork Head copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.05005368320998964, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "9f9e24c6845308-19c6f81d9bd", - "type": "box", - "name": "Fork Head copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.026472028448507212, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "e79a6ca4d5ca98-19c6f821319", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.041724730138383195, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "55eff07142dc88-19c6f825690", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.03433706295458308, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "cutlery-set", - "name": "Cutlery Set", - "children": [ - "fork-handle", - "fork-head", - "knife-handle", - "knife-blade", - "spoon-handle", - "spoon-bowl" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.696829349219807, - "y": 0.8586490872300028, - "z": 3.32 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5, - "y": 0.5, - "z": 0.5 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrktcd4-hjyk", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0025628975396361324, - "y": 0.0027103629905995495, - "z": 0.014999998435378074 - } - }, - { - "id": "0a096032464cd8-19c73df0150", - "title": "Stainless steel cutlery set", - "notes": "A set of polished stainless steel fork, knife, and spoon\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fork-handle", - "type": "box", - "name": "Fork Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.12, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003, - "z": -0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fork-head", - "type": "box", - "name": "Fork Head", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003564758820788494, - "z": 0.02485763540666619 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1703822352869635, - "y": 1, - "z": 0.17389708086299438 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "knife-handle", - "type": "box", - "name": "Knife Handle", - "dimensions": { - "width": 0.012, - "height": 0.01, - "depth": 0.1, - "edgeRadius": 0.003 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "knife-blade", - "type": "box", - "name": "Knife Blade", - "dimensions": { - "width": 0.018, - "height": 0.002, - "depth": 0.13, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.001, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-handle", - "type": "box", - "name": "Spoon Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.14, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.003, - "z": -0.03 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-bowl", - "type": "sphere", - "name": "Spoon Bowl", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.0045, - "z": 0.06 - }, - "scale": { - "x": 1.2, - "y": 0.3, - "z": 1.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "sphere", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "scale": { - "x": 0.9, - "y": 1, - "z": 0.9 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "87f20df44226d-19c6f807e4e", - "type": "box", - "name": "Fork Head copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.05005368320998964, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "9f9e24c6845308-19c6f81d9bd", - "type": "box", - "name": "Fork Head copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.026472028448507212, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "e79a6ca4d5ca98-19c6f821319", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.041724730138383195, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "55eff07142dc88-19c6f825690", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.03433706295458308, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "cutlery-set", - "name": "Cutlery Set", - "children": [ - "fork-handle", - "fork-head", - "knife-handle", - "knife-blade", - "spoon-handle", - "spoon-bowl" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -3.9286632656874647, - "y": 0.5386453681978114, - "z": 0.6449646063146555 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrktcd4-hjyk", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0025628975396361324, - "y": 0.0027103629905995495, - "z": 0.014999998435378074 - } - }, - { - "id": "0202ee297f2a88-19c73df2eb8", - "title": "Stainless steel cutlery set", - "notes": "A set of polished stainless steel fork, knife, and spoon\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fork-handle", - "type": "box", - "name": "Fork Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.12, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003, - "z": -0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fork-head", - "type": "box", - "name": "Fork Head", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003564758820788494, - "z": 0.02485763540666619 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1703822352869635, - "y": 1, - "z": 0.17389708086299438 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "knife-handle", - "type": "box", - "name": "Knife Handle", - "dimensions": { - "width": 0.012, - "height": 0.01, - "depth": 0.1, - "edgeRadius": 0.003 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "knife-blade", - "type": "box", - "name": "Knife Blade", - "dimensions": { - "width": 0.018, - "height": 0.002, - "depth": 0.13, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.001, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-handle", - "type": "box", - "name": "Spoon Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.14, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.003, - "z": -0.03 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-bowl", - "type": "sphere", - "name": "Spoon Bowl", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.0045, - "z": 0.06 - }, - "scale": { - "x": 1.2, - "y": 0.3, - "z": 1.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "sphere", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "scale": { - "x": 0.9, - "y": 1, - "z": 0.9 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "87f20df44226d-19c6f807e4e", - "type": "box", - "name": "Fork Head copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.05005368320998964, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "9f9e24c6845308-19c6f81d9bd", - "type": "box", - "name": "Fork Head copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.026472028448507212, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "e79a6ca4d5ca98-19c6f821319", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.041724730138383195, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "55eff07142dc88-19c6f825690", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.03433706295458308, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "cutlery-set", - "name": "Cutlery Set", - "children": [ - "fork-handle", - "fork-head", - "knife-handle", - "knife-blade", - "spoon-handle", - "spoon-bowl" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -3.7662372755085376, - "y": 0.5388803818702548, - "z": 0.6515124115358037 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrktcd4-hjyk", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0025628975396361324, - "y": 0.0027103629905995495, - "z": 0.014999998435378074 - } - }, - { - "id": "94ecfd5ada2578-19c73dfac8d", - "title": "Stainless steel cutlery set", - "notes": "A set of polished stainless steel fork, knife, and spoon\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "fork-handle", - "type": "box", - "name": "Fork Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.12, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003, - "z": -0.04 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "fork-head", - "type": "box", - "name": "Fork Head", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.04, - "y": 0.003564758820788494, - "z": 0.02485763540666619 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1703822352869635, - "y": 1, - "z": 0.17389708086299438 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "knife-handle", - "type": "box", - "name": "Knife Handle", - "dimensions": { - "width": 0.012, - "height": 0.01, - "depth": 0.1, - "edgeRadius": 0.003 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": -0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "knife-blade", - "type": "box", - "name": "Knife Blade", - "dimensions": { - "width": 0.018, - "height": 0.002, - "depth": 0.13, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.001, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-handle", - "type": "box", - "name": "Spoon Handle", - "dimensions": { - "width": 0.008, - "height": 0.006, - "depth": 0.14, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.003, - "z": -0.03 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "spoon-bowl", - "type": "sphere", - "name": "Spoon Bowl", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0.04, - "y": 0.0045, - "z": 0.06 - }, - "scale": { - "x": 1.2, - "y": 0.3, - "z": 1.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "sphere", - "dimensions": { - "radius": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "scale": { - "x": 0.9, - "y": 1, - "z": 0.9 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "87f20df44226d-19c6f807e4e", - "type": "box", - "name": "Fork Head copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.05005368320998964, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "9f9e24c6845308-19c6f81d9bd", - "type": "box", - "name": "Fork Head copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.026472028448507212, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "e79a6ca4d5ca98-19c6f821319", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.041724730138383195, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "55eff07142dc88-19c6f825690", - "type": "box", - "name": "Fork Head copy copy copy", - "dimensions": { - "width": 0.022, - "height": 0.004, - "depth": 0.08, - "edgeRadius": 0.001 - }, - "transform": { - "position": { - "x": -0.03433706295458308, - "y": 0.00042072598119909793, - "z": 0.045537086121765755 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.0496817507420997, - "y": 1, - "z": 0.5533031625077944 - } - }, - "material": { - "color": "#E5E4E2", - "metalness": 1, - "softness": 0.05, - "envMapIntensity": 2, - "clearcoat": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": -0.004, - "y": 0, - "z": 0.01 - } - } - }, - { - "type": "box", - "dimensions": { - "width": 0.003, - "height": 0.01, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0.004, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "cutlery-set", - "name": "Cutlery Set", - "children": [ - "fork-handle", - "fork-head", - "knife-handle", - "knife-blade", - "spoon-handle", - "spoon-bowl" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -3.4404169853090836, - "y": 0.5413681306400779, - "z": 0.6380915974649233 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrktcd4-hjyk", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0025628975396361324, - "y": 0.0027103629905995495, - "z": 0.014999998435378074 - } - }, - { - "id": "e504c978d237c-19c73e648dc", - "title": "Sleek countertop microwave oven with digital dis...", - "notes": "Sleek countertop microwave oven with digital display\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Sleek stainless steel microwave with a black glass door, digital display, and tactile control panel buttons.", - "tags": [ - "staging-asset", - "kitchen-appliance" - ], - "primitives": [ - { - "id": "mw-body", - "type": "box", - "name": "Microwave Body", - "dimensions": { - "width": 0.5, - "height": 0.3, - "depth": 0.4, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.17, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-door", - "type": "box", - "name": "Microwave Door", - "dimensions": { - "width": 0.36, - "height": 0.28, - "depth": 0.02 - }, - "transform": { - "position": { - "x": -0.06, - "y": 0.17, - "z": 0.21 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.1, - "metalness": 0.3, - "opacity": 0.95, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-panel", - "type": "box", - "name": "Control Panel", - "dimensions": { - "width": 0.12, - "height": 0.28, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.18, - "y": 0.17, - "z": 0.205 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.3, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(123%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(157%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-display", - "type": "plane", - "name": "Digital Display", - "dimensions": { - "width": 0.08, - "height": 0.04 - }, - "transform": { - "position": { - "x": 0.18, - "y": 0.26, - "z": 0.211 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "emissive": "#00FF44", - "emissiveIntensity": 2, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-handle", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.008, - "radiusBottom": 0.008, - "height": 0.22 - }, - "transform": { - "position": { - "x": -0.22, - "y": 0.17, - "z": 0.23 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E0E0E0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-foot-fl", - "type": "cylinder", - "name": "Foot FL", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.01, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.01, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-foot-fr", - "type": "cylinder", - "name": "Foot FR", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.01, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.01, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-foot-bl", - "type": "cylinder", - "name": "Foot BL", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.01, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.2, - "y": 0.01, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mw-foot-br", - "type": "cylinder", - "name": "Foot BR", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.01, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.2, - "y": 0.01, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "microwave-oven", - "name": "Countertop Microwave", - "children": [ - "mw-body", - "mw-door", - "mw-panel", - "mw-display", - "mw-handle", - "mw-foot-fl", - "mw-foot-fr", - "mw-foot-bl", - "mw-foot-br" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.59, - "y": 1.1109554476439747, - "z": 4.160795697533484 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlspo7ag-a0zw", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.16000000309199097, - "z": 0.019999998398125177 - } - }, - { - "id": "7c166d4a7b1fa-19c73e6b7ae", - "title": "Classic outdoor garden bench", - "notes": "Classic outdoor garden bench made of teak wood and iron\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "iron-leg-fl", - "type": "cylinder", - "name": "Front Left Leg", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.45 - }, - "transform": { - "position": { - "x": -0.720228292674336, - "y": 0.28014058389384716, - "z": 0.21780075093046936 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1.376947889948499, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "iron-leg-bl", - "type": "cylinder", - "name": "Back Left Leg", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": -0.72, - "y": 0.425, - "z": -0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "iron-leg-br", - "type": "cylinder", - "name": "Back Right Leg", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.85 - }, - "transform": { - "position": { - "x": 0.72, - "y": 0.425, - "z": -0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "armrest-l", - "type": "box", - "name": "Left Armrest", - "dimensions": { - "width": 0.03, - "height": 0.03, - "depth": 0.5 - }, - "transform": { - "position": { - "x": -0.72, - "y": 0.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "armrest-r", - "type": "box", - "name": "Right Armrest", - "dimensions": { - "width": 0.03, - "height": 0.03, - "depth": 0.5 - }, - "transform": { - "position": { - "x": 0.72, - "y": 0.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-seat-1", - "type": "box", - "name": "Seat Slat 1", - "dimensions": { - "width": 1.5, - "height": 0.02, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "generateTexture": "teak wood grain", - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-seat-2", - "type": "box", - "name": "Seat Slat 2", - "dimensions": { - "width": 1.5, - "height": 0.02, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-seat-3", - "type": "box", - "name": "Seat Slat 3", - "dimensions": { - "width": 1.5, - "height": 0.02, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-seat-4", - "type": "box", - "name": "Seat Slat 4", - "dimensions": { - "width": 1.5, - "height": 0.02, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": -0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-seat-5", - "type": "box", - "name": "Seat Slat 5", - "dimensions": { - "width": 1.5, - "height": 0.02, - "depth": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.45, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-back-1", - "type": "box", - "name": "Back Slat 1", - "dimensions": { - "width": 1.5, - "height": 0.08, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.55, - "z": -0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-back-2", - "type": "box", - "name": "Back Slat 2", - "dimensions": { - "width": 1.5, - "height": 0.08, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.65, - "z": -0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "teak-slat-back-3", - "type": "box", - "name": "Back Slat 3", - "dimensions": { - "width": 1.5, - "height": 0.08, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": -0.25 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#966F33", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bc2a76782b32a8-19c6fa116cf", - "type": "cylinder", - "name": "Front Left Leg copy", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.45 - }, - "transform": { - "position": { - "x": 0.72448234288688, - "y": 0.28014058389384716, - "z": 0.21867293149213518 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1.376947889948499, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "33dffc5e2a44e-19c72f3c5c9", - "name": "Bench", - "children": [ - "iron-leg-fl", - "iron-leg-bl", - "iron-leg-br", - "armrest-l", - "teak-slat-seat-1", - "armrest-r", - "teak-slat-seat-2", - "teak-slat-seat-3", - "teak-slat-seat-4", - "teak-slat-seat-5", - "teak-slat-back-1", - "teak-slat-back-2", - "teak-slat-back-3", - "bc2a76782b32a8-19c6fa116cf" - ], - "pickable": false - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 3.6298746749997752, - "y": 0.46974768857104965, - "z": 7.2158786326308135 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq36x18-whwv", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.4101636643918064, - "z": -0.009999999776482582 - } - }, - { - "id": "30e5d8717fd2e8-19c7406b70c", - "title": "Bathroom vanity and sink", - "notes": "Modern bathroom vanity with marble countertop and integrated ceramic sink\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "vanity-top", - "type": "box", - "name": "Marble Countertop", - "dimensions": { - "width": 1.22, - "height": 0.04, - "depth": 0.57 - }, - "transform": { - "position": { - "x": 1.459747195812246, - "y": 0.8479027098342572, - "z": 0.21941212353152367 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.559353042047096, - "y": 1, - "z": 1.8616339105394044 - } - }, - "material": { - "color": "#000000", - "softness": 0.96, - "metalness": 0, - "roughness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.35, - "textureHardness": 0.7, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.5, - "height": 0.1, - "depth": 0.35 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "vanity-faucet-base", - "type": "cylinder", - "name": "Faucet Base", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.05 - }, - "transform": { - "position": { - "x": 1.464583147143736, - "y": 0.9030903373388383, - "z": -0.26276045866282927 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1950343933200738, - "y": 1.1950343933200738, - "z": 1.1950343933200738 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 1, - "softness": 0.05, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "vanity-faucet-neck", - "type": "cylinder", - "name": "Faucet Neck", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.2 - }, - "transform": { - "position": { - "x": 1.464583147143736, - "y": 1.0046682607710444, - "z": -0.21495908293002627 - }, - "rotation": { - "x": 0.5000000000000004, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1950343933200738, - "y": 1.1950343933200738, - "z": 1.1950343933200738 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 1, - "softness": 0.05, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "vanity-handle-1", - "type": "box", - "name": "Drawer Handle Top", - "dimensions": { - "width": 0.4, - "height": 0.02, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 1.4751379854445872, - "y": 0.7, - "z": 0.8212718859907162 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 1, - "softness": 0.05, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "vanity-handle-2", - "type": "box", - "name": "Drawer Handle Bottom", - "dimensions": { - "width": 0.4, - "height": 0.02, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 1.4511417041676147, - "y": 0.35, - "z": 0.7354349366211226 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 1, - "softness": 0.05, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "1ab7e10ea81d88-19c73fa4da3", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0.06, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": 1.4398792470760684, - "y": 0.3637035818121548, - "z": 0.21419226769992772 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.877280004101359, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#b0b0b0", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 0.6, - "repeatY": 0.6, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "72a7582bd9f798-19c73fcad7d", - "type": "torus", - "name": "Torus", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.36, - "tube": 0.02, - "radialSegments": 16, - "tubularSegments": 48, - "arcDeg": 360 - }, - "transform": { - "position": { - "x": 1.4649259190826105, - "y": 0.9229938013889725, - "z": 0.21719273879451817 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.363769558157297, - "y": 1.1402016023454746, - "z": 3.6737982860578096 - } - }, - "material": { - "color": "#8c8c8c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "2d74ab7faed878-19c73fe2163", - "type": "cone", - "name": "Cone", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "height": 1, - "radialSegments": 32, - "heightSegments": 1, - "openEnded": 0 - }, - "transform": { - "position": { - "x": 1.4641238678877802, - "y": 0.8500336223678939, - "z": 0.21020150197384202 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.9554132102228179, - "y": 0.05643699865030326, - "z": 0.8025266297928816 - } - }, - "material": { - "color": "#a8a8a8", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - }, - { - "id": "09528ec9d32e58-19c7400c511", - "type": "sphere", - "name": "Sphere", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "radius": 0.5, - "widthSegments": 32, - "heightSegments": 16, - "phiStartDeg": 0, - "phiLengthDeg": 360, - "thetaStartDeg": 0, - "thetaLengthDeg": 180 - }, - "transform": { - "position": { - "x": 1.4688523312212294, - "y": 0.8951609546586803, - "z": 0.21191840888395186 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -3.141592653589793 - }, - "scale": { - "x": -0.12165592169681713, - "y": 0.0053739647725117015, - "z": 0.12165592169681713 - } - }, - "material": { - "color": "#787878", - "roughness": 0.12, - "softness": 0.12, - "hardness": 0.86, - "fluffiness": 0, - "metalness": 0.95, - "specularIntensity": 1.5, - "specularColor": "#ffffff", - "envMapIntensity": 1.9, - "opacity": 1, - "transmission": 0, - "ior": 2, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.45, - "clearcoatRoughness": 0.06, - "alphaCutoff": 0, - "textureSoftness": 0.1, - "textureHardness": 0.9, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "a495aa0877ba-19c74023590", - "name": "Group", - "children": [ - "vanity-top", - "vanity-faucet-base", - "vanity-faucet-neck", - "vanity-handle-1", - "vanity-handle-2", - "1ab7e10ea81d88-19c73fa4da3", - "2d74ab7faed878-19c73fe2163", - "72a7582bd9f798-19c73fcad7d", - "09528ec9d32e58-19c7400c511" - ], - "pickable": false - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 2.031272123883404, - "y": 0.6635385096694033, - "z": -4.330352147122318 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswlcio-0mjx", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 1.456095909396544, - "y": 0.4909199642562849, - "z": 0.26005917572635695 - } - }, - { - "id": "3e3ea9b2cc2e58-19c74073206", - "title": "Contemporary white ceramic toilet with sleek lin...", - "notes": "Contemporary white ceramic toilet with sleek lines and silver flush button\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Sleek skirted ceramic design with integrated tank and metallic flush button for a modern minimalist aesthetic.", - "tags": [ - "staging-asset", - "bathroom", - "toilet", - "contemporary" - ], - "primitives": [ - { - "id": "toilet-body", - "type": "box", - "name": "Toilet Body", - "dimensions": { - "width": 0.38, - "height": 0.4, - "depth": 0.6, - "edgeRadius": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.1, - "metalness": 0, - "clearcoat": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toilet-tank", - "type": "box", - "name": "Toilet Tank", - "dimensions": { - "width": 0.38, - "height": 0.35, - "depth": 0.2, - "edgeRadius": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.575, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.1, - "metalness": 0, - "clearcoat": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toilet-seat", - "type": "box", - "name": "Toilet Seat", - "dimensions": { - "width": 0.38, - "height": 0.02, - "depth": 0.4, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.41, - "z": 0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toilet-lid", - "type": "box", - "name": "Toilet Lid", - "dimensions": { - "width": 0.39, - "height": 0.02, - "depth": 0.41, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.43, - "z": 0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.1, - "metalness": 0, - "clearcoat": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "flush-button", - "type": "cylinder", - "name": "Flush Button", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.04, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.755, - "z": -0.2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 1, - "envMapIntensity": 1.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "contemporary-toilet-group", - "name": "Contemporary Toilet", - "children": [ - "toilet-body", - "toilet-tank", - "toilet-seat", - "toilet-lid", - "flush-button" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 4.454104862841407, - "y": 0.42316131113123934, - "z": -4.505567046787334 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3957356681124615, - "y": 1.3957356681124615, - "z": 1.3957356681124615 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswltq1-zo59", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.37999999845400456, - "z": 0.0024999931454658397 - } - }, - { - "id": "94e9a636240718-19c7407bbee", - "title": "Luxury rainfall shower head and wall-mounted mix...", - "notes": "Luxury rainfall shower head and wall-mounted mixer valve assembly\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "bathroom", - "fixture" - ], - "primitives": [ - { - "id": "shower-wall-strip", - "type": "box", - "name": "Mounting Wall Section", - "dimensions": { - "width": 0.5, - "height": 2.5, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 1.25, - "z": -0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.8, - "metalness": 0, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "shower-arm-pipe", - "type": "cylinder", - "name": "Shower Arm", - "dimensions": { - "radiusTop": 0.015, - "radiusBottom": 0.015, - "height": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 2.3, - "z": 0.2 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "shower-head-plate", - "type": "box", - "name": "Rainfall Head", - "dimensions": { - "width": 0.3, - "height": 0.02, - "depth": 0.3, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 2.28, - "z": 0.4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D3D3D3", - "softness": 0.1, - "metalness": 1, - "generateTexture": "brushed chrome metal with small grid of water nozzles", - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(219%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(253%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "shower-mixer-backplate", - "type": "box", - "name": "Mixer Plate", - "dimensions": { - "width": 0.12, - "height": 0.2, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 1.1, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "shower-mixer-lever", - "type": "box", - "name": "Mixer Handle", - "dimensions": { - "width": 0.02, - "height": 0.12, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 1.1, - "z": 0.03 - }, - "rotation": { - "x": 0.2, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E5E4E2", - "softness": 0.05, - "metalness": 1, - "roughness": 0.05, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "luxury-shower-assembly", - "name": "Luxury Rainfall Shower Set", - "children": [ - "shower-wall-strip", - "shower-arm-pipe", - "shower-head-plate", - "shower-mixer-backplate", - "shower-mixer-lever" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.9425969244634211, - "y": 1.3148567749089612, - "z": -0.36640908300765984 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswp3v8-4kfa", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 1.25, - "z": 0.26500000309199095 - } - }, - { - "id": "a2b85a007ca9d-19c7408763c", - "title": "Woven wicker laundry hamper with a fabric liner ...", - "notes": "Woven wicker laundry hamper with a fabric liner and lid\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "bathroom", - "laundry" - ], - "primitives": [ - { - "id": "hamper-body", - "type": "cylinder", - "name": "Wicker Body", - "dimensions": { - "radiusTop": 0.2, - "radiusBottom": 0.18, - "height": 0.55 - }, - "transform": { - "position": { - "x": 0, - "y": 0.275, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#A07855", - "softness": 0.8, - "metalness": 0, - "generateTexture": "woven wicker pattern, light brown natural fibers", - "textureHardness": 0.5, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(137%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(171%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "hamper-liner-rim", - "type": "cylinder", - "name": "Fabric Liner Rim", - "dimensions": { - "radiusTop": 0.205, - "radiusBottom": 0.205, - "height": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0.52, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5DC", - "softness": 1, - "fluffiness": 0.3, - "generateTexture": "off-white linen fabric texture", - "textureSoftness": 0.5, - "roughness": 1, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "hamper-lid", - "type": "cylinder", - "name": "Wicker Lid", - "dimensions": { - "radiusTop": 0.21, - "radiusBottom": 0.21, - "height": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.565, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#A07855", - "softness": 0.8, - "metalness": 0, - "generateTexture": "woven wicker pattern, circular weave", - "textureHardness": 0.5, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(63%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(97%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "hamper-lid-handle", - "type": "box", - "name": "Lid Handle", - "dimensions": { - "width": 0.08, - "height": 0.02, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.585, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B5A2B", - "softness": 0.5, - "metalness": 0, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "wicker-hamper-group", - "name": "Wicker Laundry Hamper", - "children": [ - "hamper-body", - "hamper-liner-rim", - "hamper-lid", - "hamper-lid-handle" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 3.25028196420934, - "y": 0.3302800075837711, - "z": -4.503918870307929 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.8583300892907466, - "y": 0.8583300892907466, - "z": 0.8583300892907466 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswq6fr-b60u", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.29749999690800905, - "z": 0 - } - }, - { - "id": "99c77c594d079-19c7408ea1d", - "title": "Polished chrome wall-mounted towel rack with fol...", - "notes": "Polished chrome wall-mounted towel rack with folded plush white towels\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Polished chrome rack with high metalness and soft, high-fluffiness white towels for a realistic bathroom aesthetic.", - "tags": [ - "staging-asset", - "bathroom", - "towel-rack" - ], - "primitives": [ - { - "id": "rack-backplate-l", - "type": "cylinder", - "name": "Left Backplate", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.01 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.2, - "z": -0.1 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "rack-backplate-r", - "type": "cylinder", - "name": "Right Backplate", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.2, - "z": -0.1 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "rack-arm-l", - "type": "cylinder", - "name": "Left Arm", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.2, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "rack-arm-r", - "type": "cylinder", - "name": "Right Arm", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.2, - "z": 0 - }, - "rotation": { - "x": 1.5708, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "rack-bar-front", - "type": "cylinder", - "name": "Front Bar", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2, - "z": 0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "rack-bar-mid", - "type": "cylinder", - "name": "Middle Bar", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.62 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2, - "z": 0.02 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 0.1, - "metalness": 1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "towel-folded-1", - "type": "box", - "name": "Folded Towel Bottom", - "dimensions": { - "width": 0.5, - "height": 0.08, - "depth": 0.18, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.25, - "z": 0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.9, - "metalness": 0, - "fluffiness": 0.8, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(11%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(45%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "towel-folded-2", - "type": "box", - "name": "Folded Towel Top", - "dimensions": { - "width": 0.45, - "height": 0.07, - "depth": 0.16, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.32, - "z": 0.05 - }, - "rotation": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.9, - "metalness": 0, - "fluffiness": 0.8, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "chrome-towel-rack-set", - "name": "Towel Rack with Towels", - "children": [ - "rack-backplate-l", - "rack-backplate-r", - "rack-arm-l", - "rack-arm-r", - "rack-bar-front", - "rack-bar-mid", - "towel-folded-1", - "towel-folded-2" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.2000208284940208, - "y": 1.53, - "z": -1.1057607374920708 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.4494794852373833, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswo0ix-3ujf", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.26249999122687057, - "z": 0.01807261087466902 - } - }, - { - "id": "7e1f8523afa01-19c74098514", - "title": "Minimalist floating wooden wall shelves for bath...", - "notes": "Minimalist floating wooden wall shelves for bathroom toiletries\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "bathroom-decor", - "floating-shelves" - ], - "primitives": [ - { - "id": "shelf-lower", - "type": "box", - "name": "Lower Shelf", - "dimensions": { - "width": 0.6, - "height": 0.03, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0, - "y": 1.215, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C19A6B", - "softness": 0.7, - "metalness": 0, - "generateTexture": "light oak wood grain", - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(148%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(182%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "shelf-upper", - "type": "box", - "name": "Upper Shelf", - "dimensions": { - "width": 0.6, - "height": 0.03, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0, - "y": 1.55, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C19A6B", - "softness": 0.7, - "metalness": 0, - "generateTexture": "light oak wood grain", - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(148%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(182%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "soap-bottle", - "type": "cylinder", - "name": "Soap Bottle", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.18, - "y": 1.28, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.2, - "metalness": 0.1, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "soap-pump-stem", - "type": "cylinder", - "name": "Pump Stem", - "dimensions": { - "radiusTop": 0.005, - "radiusBottom": 0.005, - "height": 0.03 - }, - "transform": { - "position": { - "x": -0.18, - "y": 1.345, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "soap-pump-head", - "type": "box", - "name": "Pump Head", - "dimensions": { - "width": 0.04, - "height": 0.01, - "depth": 0.015 - }, - "transform": { - "position": { - "x": -0.165, - "y": 1.36, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "glass-jar", - "type": "cylinder", - "name": "Glass Jar", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.04, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0.05, - "y": 1.27, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "opacity": 0.3, - "transmission": 0.9, - "ior": 1.5, - "thickness": 0.01, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plant-pot", - "type": "cylinder", - "name": "Succulent Pot", - "dimensions": { - "radiusTop": 0.045, - "radiusBottom": 0.035, - "height": 0.06 - }, - "transform": { - "position": { - "x": 0.15, - "y": 1.595, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E2725B", - "softness": 0.8, - "metalness": 0, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "plant-foliage", - "type": "sphere", - "name": "Succulent Plant", - "dimensions": { - "radius": 0.04 - }, - "transform": { - "position": { - "x": 0.15, - "y": 1.64, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#6B8E23", - "softness": 0.9, - "fluffiness": 0.2, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "floating-toiletries-shelves", - "name": "Floating Toiletries Shelves", - "children": [ - "shelf-lower", - "shelf-upper", - "soap-bottle", - "soap-pump-stem", - "soap-pump-head", - "glass-jar", - "plant-pot", - "plant-foliage" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 4.491771043106426, - "y": 1.2101214572034933, - "z": -0.15247527825167362 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.1833166424840937, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswr8h6-y31x", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 1.4399999997206032, - "z": 0.07500000000000001 - } - }, - { - "id": "ba520c65d11258-19c7409f996", - "title": "Matching ceramic soap dispenser, toothbrush hold...", - "notes": "Matching ceramic soap dispenser, toothbrush holder, and soap dish set\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Coordinated ceramic set with glossy porcelain finish and chrome pump, sized for standard bathroom vanity placement.", - "tags": [ - "staging-asset", - "bathroom", - "accessories" - ], - "primitives": [ - { - "id": "soap-dispenser-body", - "type": "cylinder", - "name": "Dispenser Body", - "dimensions": { - "radiusTop": 0.035, - "radiusBottom": 0.035, - "height": 0.15 - }, - "transform": { - "position": { - "x": -0.1, - "y": 0.075, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(87%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(121%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "soap-dispenser-pump-base", - "type": "cylinder", - "name": "Pump Base", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.02 - }, - "transform": { - "position": { - "x": -0.1, - "y": 0.16, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "soap-dispenser-nozzle", - "type": "box", - "name": "Pump Nozzle", - "dimensions": { - "width": 0.04, - "height": 0.01, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.12, - "y": 0.165, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "toothbrush-holder-body", - "type": "cylinder", - "name": "Toothbrush Holder", - "dimensions": { - "radiusTop": 0.035, - "radiusBottom": 0.035, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(87%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(121%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "cylinder", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.09 - }, - "transform": { - "position": { - "x": 0, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "soap-dish-body", - "type": "box", - "name": "Soap Dish", - "dimensions": { - "width": 0.12, - "height": 0.02, - "depth": 0.08, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0.12, - "y": 0.01, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.2, - "metalness": 0, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(87%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(121%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.1, - "height": 0.015, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - } - ], - "lights": [], - "groups": [ - { - "id": "bathroom-accessory-set", - "name": "Ceramic Bathroom Set", - "children": [ - "soap-dispenser-body", - "soap-dispenser-pump-base", - "soap-dispenser-nozzle", - "toothbrush-holder-body", - "soap-dish-body" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 1.3341187797982013, - "y": 1.1268225562070888, - "z": -4.449625881687194 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswslwr-r3wc", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.019999999552965164, - "y": 0.08499999845400452, - "z": 0 - } - }, - { - "id": "9ea7f64c32fb7-19c740b89fc", - "title": "Wall-mounted medicine cabinet with a large mirro...", - "notes": "Wall-mounted medicine cabinet with a large mirrored door and internal shelving\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "cabinet-body", - "type": "box", - "name": "Cabinet Frame", - "dimensions": { - "width": 0.6, - "height": 0.8, - "depth": 0.15 - }, - "transform": { - "position": { - "x": -0.039414245552734845, - "y": 0.4, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7298934361617562, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.56, - "height": 0.76, - "depth": 0.14 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "cabinet-shelf-1", - "type": "box", - "name": "Internal Shelf Low", - "dimensions": { - "width": 0.56, - "height": 0.015, - "depth": 0.13 - }, - "transform": { - "position": { - "x": -0.039414245552734845, - "y": 0.25, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7298934361617562, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.3, - "opacity": 0.9, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "cabinet-shelf-2", - "type": "box", - "name": "Internal Shelf High", - "dimensions": { - "width": 0.56, - "height": 0.015, - "depth": 0.13 - }, - "transform": { - "position": { - "x": -0.039414245552734845, - "y": 0.55, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7298934361617562, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.3, - "opacity": 0.9, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "cabinet-mirror-door", - "type": "box", - "name": "Mirrored Door", - "dimensions": { - "width": 0.6, - "height": 0.8, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.039414245552734845, - "y": 0.4, - "z": 0.08 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7298934361617562, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#adadad", - "metalness": 1, - "softness": 0, - "envMapIntensity": 3, - "roughness": 0, - "hardness": 1, - "fluffiness": 0, - "specularIntensity": 1.7, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 2.2, - "thickness": 0, - "attenuationColor": "#969696", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0.95, - "clearcoat": 1, - "clearcoatRoughness": 0, - "textureSoftness": 0, - "textureHardness": 1, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "cabinet-handle", - "type": "cylinder", - "name": "Door Handle", - "dimensions": { - "radiusTop": 0.006, - "radiusBottom": 0.006, - "height": 0.06 - }, - "transform": { - "position": { - "x": 0.4276569822109394, - "y": 0.4, - "z": 0.09 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7298934361617562, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#c0c0c0", - "metalness": 1, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "medicine-cabinet-group", - "name": "Medicine Cabinet", - "children": [ - "cabinet-body", - "cabinet-shelf-1", - "cabinet-shelf-2", - "cabinet-mirror-door", - "cabinet-handle" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 2.017737545060936, - "y": 1.9269932324049326, - "z": -4.810104392332999 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.303586220666433, - "y": 1.1705937734630785, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlswnc5t-6tfx", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.03941424555273487, - "y": 0.4, - "z": 0.022500016540531666 - } - }, - { - "id": "b45b699007c72-19c74300203", - "title": "Framed abstract oil painting", - "notes": "Large framed abstract oil painting with vibrant colors and thick brushstrokes\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "art-frame", - "type": "box", - "name": "Picture Frame", - "dimensions": { - "width": 1.24, - "height": 0.94, - "depth": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 1.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.0262510636731648, - "y": 1.02267477099897, - "z": 1 - } - }, - "material": { - "color": "#b48550", - "softness": 0.2, - "metalness": 0.1, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 1.2, - "height": 0.9, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "_colliderHandle": null - }, - { - "id": "art-canvas", - "type": "box", - "name": "Canvas Surface", - "dimensions": { - "width": 1.2, - "height": 0.9, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 1.5, - "z": 0.01347674573255242 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.9, - "metalness": 0, - "generateTexture": "vibrant abstract oil painting, thick impasto brushstrokes, colorful expressionism", - "textureHardness": 0.25, - "roughness": 0.9, - "hardness": 0.15, - "fluffiness": 0, - "specularIntensity": 0.4, - "specularColor": "#ffffff", - "envMapIntensity": 0.25, - "opacity": 1, - "transmission": 0, - "ior": 1.52, - "thickness": 0, - "attenuationColor": "#9ad07a", - "attenuationDistance": 0.45, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.08, - "clearcoatRoughness": 0.7, - "textureSoftness": 0.55, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/048224345952.webp" - }, - "castShadow": false, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "abstract-painting-group", - "name": "Large Abstract Oil Painting", - "children": [ - "art-frame", - "art-canvas" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 4.424584573788344, - "y": 1.9470570099444073, - "z": 0.08375157009131762 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.3643420085814393, - "y": 1.2248492171872085, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkyvvc-otav", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 1.5, - "z": 0.0017383729780349189 - } - }, - { - "id": "0d071b9d15a778-19c74323057", - "title": "Framed landscape oil painting", - "notes": "Classic framed landscape oil painting of a serene forest and lake\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "landscape-frame-main", - "type": "box", - "name": "Picture Frame", - "dimensions": { - "width": 0.9, - "height": 0.6, - "depth": 0.04, - "edgeRadius": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.4, - "metalness": 0.1, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/2b666e22f0a6.jpg" - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.8, - "height": 0.5, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0.01 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "_colliderHandle": null - }, - { - "id": "landscape-canvas-art", - "type": "box", - "name": "Oil Painting Canvas", - "dimensions": { - "width": 0.8, - "height": 0.5, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.3, - "z": 0.02021895082948882 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#666666", - "softness": 0.9, - "metalness": 0, - "generateTexture": "classic oil painting of a serene forest and lake, thick brushstrokes, rich colors", - "roughness": 0.9, - "hardness": 0.15, - "fluffiness": 0, - "specularIntensity": 0.4, - "specularColor": "#ffffff", - "envMapIntensity": 0.25, - "opacity": 1, - "transmission": 0, - "ior": 1.52, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1.2, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.08, - "clearcoatRoughness": 0.7, - "textureSoftness": 0.55, - "textureHardness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/1c8ef077e400.jpg" - }, - "physics": false, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "landscape-painting-asset", - "name": "Framed Landscape Painting", - "children": [ - "landscape-frame-main", - "landscape-canvas-art" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -3.608992492739797, - "y": 1.7361845431468765, - "z": -4.879999937970252 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 2.321890853006333, - "y": 2.518815242069379, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrkzi2t-3mm1", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.30000000000000004, - "z": 0.002609475582382474 - } - }, - { - "id": "8f61d882176858-19c743358f5", - "title": "Vintage poster", - "notes": "Vintage style kitchen advertisement poster for coffee or bread in a wooden frame\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "poster-frame-back", - "type": "box", - "name": "Frame Backing", - "dimensions": { - "width": 0.45, - "height": 0.65, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5D4037", - "softness": 0.7, - "metalness": 0.1, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "poster-art-surface", - "type": "plane", - "name": "Poster Art", - "dimensions": { - "width": 0.4, - "height": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": 0.010964537656762963 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0, - "generateTexture": "vintage 1950s coffee advertisement poster, distressed paper, warm tones", - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 0, - "specularColor": "#ffffff", - "envMapIntensity": 0, - "opacity": 1, - "transmission": 0.66, - "ior": 1, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 0.01, - "iridescence": 0.08, - "emissive": "#88aaff", - "emissiveIntensity": 0, - "clearcoat": 0.13, - "clearcoatRoughness": 0, - "textureSoftness": 0, - "textureHardness": 0, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 0.6, - "repeatY": 1.2, - "offsetX": 0.07, - "offsetY": -0.01, - "rotationDeg": 0 - }, - "metalness": 0, - "texturePath": "textures/3885a68cb0cb.webp" - }, - "castShadow": false, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "poster-glass-cover", - "type": "plane", - "name": "Poster Glass", - "dimensions": { - "width": 0.4, - "height": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.325, - "z": 0.012 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "opacity": 0.1, - "transmission": 0.95, - "ior": 1.5, - "thickness": 0.002, - "softness": 0, - "doubleSided": true, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "frame-border-left", - "type": "box", - "name": "Frame Border Left", - "dimensions": { - "width": 0.025, - "height": 0.65, - "depth": 0.03 - }, - "transform": { - "position": { - "x": -0.2125, - "y": 0.325, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "frame-border-right", - "type": "box", - "name": "Frame Border Right", - "dimensions": { - "width": 0.025, - "height": 0.65, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.2125, - "y": 0.325, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "frame-border-top", - "type": "box", - "name": "Frame Border Top", - "dimensions": { - "width": 0.45, - "height": 0.025, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6375, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "frame-border-bottom", - "type": "box", - "name": "Frame Border Bottom", - "dimensions": { - "width": 0.45, - "height": 0.025, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.012500000000000011, - "z": 0.005 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3E2723", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "vintage-coffee-poster", - "name": "Vintage Coffee Poster", - "children": [ - "poster-frame-back", - "poster-art-surface", - "poster-glass-cover", - "frame-border-left", - "frame-border-right", - "frame-border-top", - "frame-border-bottom" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.4079772102754955, - "y": 1.8962601991548873, - "z": -0.06366045843035233 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 2.0446682506880354, - "y": 2.1001934576595302, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl2tow-d42j", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.32499999999999996, - "z": 0.004999999944120646 - } - }, - { - "id": "46aa48c4bb14d-19c74347b30", - "title": "wall clock", - "notes": "Large industrial style metal wall clock with Roman numerals\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "clock-frame", - "type": "torus", - "name": "Outer Frame", - "dimensions": { - "radius": 0.4, - "tube": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1a1a1a", - "softness": 0.2, - "metalness": 0.9, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "clock-face", - "type": "cylinder", - "name": "Clock Face", - "dimensions": { - "radiusTop": 0.39, - "radiusBottom": 0.39, - "height": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4, - "z": -0.01 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#919191", - "softness": 0.4, - "metalness": 0.2, - "generateTexture": "vintage white clock face with large black industrial roman numerals and weathered texture", - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "clock-hand-hour", - "type": "box", - "name": "Hour Hand", - "dimensions": { - "width": 0.02, - "height": 0.22, - "depth": 0.005 - }, - "transform": { - "position": { - "x": 0.053, - "y": 0.495, - "z": 0.01 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -0.5200000000000001 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#000000", - "softness": 0.1, - "metalness": 0.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "clock-hand-minute", - "type": "box", - "name": "Minute Hand", - "dimensions": { - "width": 0.015, - "height": 0.32, - "depth": 0.005 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.53, - "z": 0.015 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.5200000000000001 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#000000", - "softness": 0.1, - "metalness": 0.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "clock-center-pin", - "type": "cylinder", - "name": "Center Pin", - "dimensions": { - "radiusTop": 0.02, - "radiusBottom": 0.02, - "height": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.4, - "z": 0.015 - }, - "rotation": { - "x": 1.5708000000000006, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "metalness": 0.9, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "industrial-wall-clock", - "name": "Large Industrial Wall Clock", - "children": [ - "clock-frame", - "clock-face", - "clock-hand-hour", - "clock-hand-minute", - "clock-center-pin" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 5.862637492866764, - "y": 2.36, - "z": 2.88 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl3nqu-yx60", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.4, - "z": 0.005000036787878975 - } - }, - { - "id": "ba2c0f11cedef-19c743507ed", - "title": "Wall mirror", - "notes": "Large rectangular wall mirror with an ornate antique gold leaf frame\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "mirror-frame-main", - "type": "box", - "name": "Gold Frame Base", - "dimensions": { - "width": 1, - "height": 1.5, - "depth": 0.06 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.8, - "softness": 0.2, - "generateTexture": "ornate antique gold leaf carvings, baroque style", - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.8, - "height": 1.3, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0, - "z": 0 - } - } - } - ], - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "mirror-glass", - "type": "box", - "name": "Mirror Glass", - "dimensions": { - "width": 0.82, - "height": 1.32, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.75, - "z": 0.02958877873659403 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "metalness": 1, - "softness": 0, - "envMapIntensity": 2, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "corner-decoration-tl", - "type": "torus", - "name": "Corner Ornament TL", - "dimensions": { - "radius": 0.04, - "tube": 0.015 - }, - "transform": { - "position": { - "x": -0.45, - "y": 1.45, - "z": 0.03 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "corner-decoration-tr", - "type": "torus", - "name": "Corner Ornament TR", - "dimensions": { - "radius": 0.04, - "tube": 0.015 - }, - "transform": { - "position": { - "x": 0.45, - "y": 1.45, - "z": 0.03 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "corner-decoration-bl", - "type": "torus", - "name": "Corner Ornament BL", - "dimensions": { - "radius": 0.04, - "tube": 0.015 - }, - "transform": { - "position": { - "x": -0.45, - "y": 0.050000000000000044, - "z": 0.03 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "corner-decoration-br", - "type": "torus", - "name": "Corner Ornament BR", - "dimensions": { - "radius": 0.04, - "tube": 0.015 - }, - "transform": { - "position": { - "x": 0.45, - "y": 0.050000000000000044, - "z": 0.03 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D4AF37", - "metalness": 0.9, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ornate-mirror-group", - "name": "Ornate Antique Mirror", - "children": [ - "mirror-frame-main", - "mirror-glass", - "corner-decoration-tl", - "corner-decoration-tr", - "corner-decoration-bl", - "corner-decoration-br" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.8721821641931122, - "y": 1.8546174723983981, - "z": 3.9556118892043464 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl4qo4-xzyd", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.75, - "z": 0.027505963917087026 - } - }, - { - "id": "39e52be49c9ec8-19c74363fc3", - "title": "Assorted decorative plants", - "notes": "Assorted decorative indoor and outdoor plants in ceramic pots\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "interactions": [], - "scene": { - "tags": [], - "primitives": [ - { - "id": "pot-white", - "type": "cylinder", - "name": "Tall White Pot", - "dimensions": { - "radiusTop": 0.12, - "radiusBottom": 0.1, - "height": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.2077895514358461, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#828fce", - "softness": 0.25, - "metalness": 0, - "clearcoat": 0.65, - "roughness": 0.25, - "hardness": 0.55, - "fluffiness": 0, - "specularIntensity": 1.2, - "specularColor": "#ffffff", - "envMapIntensity": 1.1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%27512%27%20height%3D%27512%27%20viewBox%3D%270%200%20512%20512%27%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D%27g%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%271%27%3E%0A%20%20%20%20%3Cstop%20offset%3D%270%25%27%20stop-color%3D%27hsl%28146%2C22%25%2C42%25%29%27/%3E%0A%20%20%20%20%3Cstop%20offset%3D%27100%25%27%20stop-color%3D%27hsl%28180%2C24%25%2C55%25%29%27/%3E%0A%20%20%3C/linearGradient%3E%0A%20%20%3Cpattern%20id%3D%27p%27%20width%3D%2732%27%20height%3D%2732%27%20patternUnits%3D%27userSpaceOnUse%27%3E%0A%20%20%20%20%3Crect%20width%3D%2732%27%20height%3D%2732%27%20fill%3D%27none%27/%3E%0A%20%20%20%20%3Cpath%20d%3D%27M0%2031%20L31%200%27%20stroke%3D%27rgba%28255%2C255%2C255%2C0.14%29%27%20stroke-width%3D%271%27/%3E%0A%20%20%3C/pattern%3E%0A%3C/defs%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23g%29%27/%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23p%29%27/%3E%0A%3C/svg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0.08, - "textureSoftness": 0.15, - "textureHardness": 0.75, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-terracotta", - "type": "cylinder", - "name": "Small Terracotta Pot", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.06, - "height": 0.12 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.06, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E2725B", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%27512%27%20height%3D%27512%27%20viewBox%3D%270%200%20512%20512%27%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D%27g%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%271%27%3E%0A%20%20%20%20%3Cstop%20offset%3D%270%25%27%20stop-color%3D%27hsl%28186%2C22%25%2C42%25%29%27/%3E%0A%20%20%20%20%3Cstop%20offset%3D%27100%25%27%20stop-color%3D%27hsl%28220%2C24%25%2C55%25%29%27/%3E%0A%20%20%3C/linearGradient%3E%0A%20%20%3Cpattern%20id%3D%27p%27%20width%3D%2732%27%20height%3D%2732%27%20patternUnits%3D%27userSpaceOnUse%27%3E%0A%20%20%20%20%3Crect%20width%3D%2732%27%20height%3D%2732%27%20fill%3D%27none%27/%3E%0A%20%20%20%20%3Cpath%20d%3D%27M0%2031%20L31%200%27%20stroke%3D%27rgba%28255%2C255%2C255%2C0.14%29%27%20stroke-width%3D%271%27/%3E%0A%20%20%3C/pattern%3E%0A%3C/defs%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23g%29%27/%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23p%29%27/%3E%0A%3C/svg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-grey", - "type": "cylinder", - "name": "Medium Grey Pot", - "dimensions": { - "radiusTop": 0.15, - "radiusBottom": 0.12, - "height": 0.25 - }, - "transform": { - "position": { - "x": -0.3, - "y": 0.125, - "z": 0.05 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#787878", - "softness": 0.9, - "metalness": 0, - "roughness": 0.9, - "hardness": 0.15, - "fluffiness": 0, - "specularIntensity": 0.4, - "specularColor": "#ffffff", - "envMapIntensity": 0.25, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%27512%27%20height%3D%27512%27%20viewBox%3D%270%200%20512%20512%27%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D%27g%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%271%27%3E%0A%20%20%20%20%3Cstop%20offset%3D%270%25%27%20stop-color%3D%27hsl%2811%2C22%25%2C42%25%29%27/%3E%0A%20%20%20%20%3Cstop%20offset%3D%27100%25%27%20stop-color%3D%27hsl%2845%2C24%25%2C55%25%29%27/%3E%0A%20%20%3C/linearGradient%3E%0A%20%20%3Cpattern%20id%3D%27p%27%20width%3D%2732%27%20height%3D%2732%27%20patternUnits%3D%27userSpaceOnUse%27%3E%0A%20%20%20%20%3Crect%20width%3D%2732%27%20height%3D%2732%27%20fill%3D%27none%27/%3E%0A%20%20%20%20%3Cpath%20d%3D%27M0%2031%20L31%200%27%20stroke%3D%27rgba%28255%2C255%2C255%2C0.14%29%27%20stroke-width%3D%271%27/%3E%0A%20%20%3C/pattern%3E%0A%3C/defs%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23g%29%27/%3E%0A%3Crect%20width%3D%27512%27%20height%3D%27512%27%20fill%3D%27url%28%23p%29%27/%3E%0A%3C/svg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.52, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.08, - "clearcoatRoughness": 0.7, - "textureSoftness": 0.55, - "textureHardness": 0.25, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "snake-1", - "type": "box", - "name": "Snake Plant Leaf 1", - "dimensions": { - "width": 0.06, - "height": 0.6, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.7, - "z": 0 - }, - "rotation": { - "x": 0.1, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2E8B57", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "snake-2", - "type": "box", - "name": "Snake Plant Leaf 2", - "dimensions": { - "width": 0.05, - "height": 0.5, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0.03, - "y": 0.65, - "z": 0.03 - }, - "rotation": { - "x": -0.1, - "y": 0.5, - "z": 0.1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2E8B57", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "snake-3", - "type": "box", - "name": "Snake Plant Leaf 3", - "dimensions": { - "width": 0.05, - "height": 0.55, - "depth": 0.01 - }, - "transform": { - "position": { - "x": -0.03, - "y": 0.675, - "z": -0.03 - }, - "rotation": { - "x": 0.05, - "y": -0.5, - "z": -0.1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2E8B57", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "succulent", - "type": "sphere", - "name": "Succulent Plant", - "dimensions": { - "radius": 0.07 - }, - "transform": { - "position": { - "x": 0.3, - "y": 0.16, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8FBC8F", - "softness": 0.6, - "roughness": 0.6, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bush-1", - "type": "cone", - "name": "Bushy Leaf 1", - "dimensions": { - "radius": 0.15, - "height": 0.3 - }, - "transform": { - "position": { - "x": -0.32, - "y": 0.4, - "z": 0.07 - }, - "rotation": { - "x": 0.4, - "y": 0, - "z": 0.4 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#228B22", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bush-2", - "type": "cone", - "name": "Bushy Leaf 2", - "dimensions": { - "radius": 0.15, - "height": 0.3 - }, - "transform": { - "position": { - "x": -0.28, - "y": 0.4, - "z": 0.03 - }, - "rotation": { - "x": -0.4, - "y": 3.14, - "z": -0.4 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#228B22", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "plant-arrangement", - "name": "Assorted Plant Arrangement", - "children": [ - "pot-white", - "pot-terracotta", - "pot-grey", - "snake-1", - "snake-2", - "snake-3", - "succulent", - "bush-1", - "bush-2" - ] - } - ] - } - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -2.9044516167844154, - "y": 0.6125478909283358, - "z": 5.464666979023578 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlq381sx-2beb", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": -0.06828595502296761, - "y": 0.4995002142584293, - "z": 0.05005857882475222 - } - }, - { - "id": "412ff4ad131ff-19c74372469", - "title": "Set of three canvas prints featuring detailed bo...", - "notes": "Set of three canvas prints featuring detailed botanical illustrations of ferns and leaves\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "botanical-canvas-left", - "type": "box", - "name": "Left Fern Canvas", - "dimensions": { - "width": 0.4, - "height": 0.6, - "depth": 0.03 - }, - "transform": { - "position": { - "x": -0.5, - "y": 0.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#fdf5e6", - "softness": 0.8, - "metalness": 0, - "generateTexture": "detailed botanical illustration of a green fern on cream canvas", - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(0%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(34%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "botanical-canvas-center", - "type": "box", - "name": "Center Leaf Canvas", - "dimensions": { - "width": 0.4, - "height": 0.6, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#fdf5e6", - "softness": 0.8, - "metalness": 0, - "generateTexture": "detailed botanical illustration of a monstera leaf on cream canvas", - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(317%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(351%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "botanical-canvas-right", - "type": "box", - "name": "Right Fern Canvas", - "dimensions": { - "width": 0.4, - "height": 0.6, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.5, - "y": 0.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#fdf5e6", - "softness": 0.8, - "metalness": 0, - "generateTexture": "detailed botanical illustration of a forest leaf on cream canvas", - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(304%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(338%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "botanical-canvas-set", - "name": "Botanical Canvas Set", - "children": [ - "botanical-canvas-left", - "botanical-canvas-center", - "botanical-canvas-right" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.890814574323871, - "y": 2.1254741150774024, - "z": -2.4937709011301235 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.7741064961903168, - "y": 1.3607416183002867, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl5ck7-6u9t", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.30000000000000004, - "z": 0 - } - }, - { - "id": "048418404b31-19c7437cce5", - "title": "Neon light wall art", - "notes": "Modern neon light wall art in the shape of a lightning bolt\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "neon-bolt-top", - "type": "cylinder", - "name": "Neon Tube Top", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.22 - }, - "transform": { - "position": { - "x": 0.06, - "y": 0.52, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.6 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#ff000d", - "emissive": "#0a0000", - "emissiveIntensity": 5, - "softness": 0, - "metalness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#e00000", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ff0000", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "neon-bolt-mid", - "type": "cylinder", - "name": "Neon Tube Middle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.18 - }, - "transform": { - "position": { - "x": 0.046, - "y": 0.381, - "z": -9.088606137019895e-06 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#a20202", - "emissive": "#6a0101", - "emissiveIntensity": 5, - "softness": 0, - "metalness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#660000", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#850000", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "neon-bolt-bot", - "type": "cylinder", - "name": "Neon Tube Bottom", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.35 - }, - "transform": { - "position": { - "x": 0.038, - "y": 0.171, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.39999999999999997 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#b80000", - "emissive": "#800000", - "emissiveIntensity": 5, - "softness": 0, - "metalness": 0, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#cc0000", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#d60000", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "neon-mount-1", - "type": "box", - "name": "Wall Mount Top", - "dimensions": { - "width": 0.02, - "height": 0.02, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.06, - "y": 0.52, - "z": -0.015 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "neon-mount-2", - "type": "box", - "name": "Wall Mount Bottom", - "dimensions": { - "width": 0.02, - "height": 0.02, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.038, - "y": 0.171, - "z": -0.015 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#222222", - "softness": 0.2, - "metalness": 0.8, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "modern-neon-lightning-bolt", - "name": "Modern Neon Lightning Bolt", - "children": [ - "neon-bolt-top", - "neon-bolt-mid", - "neon-bolt-bot", - "neon-mount-1", - "neon-mount-2" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 0.9, - "y": 1.65, - "z": -0.94 - }, - "rotation": { - "x": 0, - "y": 4.71238898038469, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl6mfa-yr65", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.0455026046119541, - "y": 0.31117674360714764, - "z": -0.009999999944120645 - } - }, - { - "id": "eedf6074a66ce-19c7438a204", - "title": "Bohemian style woven macrame", - "notes": "Bohemian style woven macrame wall hanging with fringe and wooden dowel\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "macrame-dowel", - "type": "cylinder", - "name": "Wooden Dowel", - "dimensions": { - "radiusTop": 0.012, - "radiusBottom": 0.012, - "height": 0.8 - }, - "transform": { - "position": { - "x": 0, - "y": 0.8, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708000000000006 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#633713", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "macrame-body", - "type": "box", - "name": "Woven Body", - "dimensions": { - "width": 0.6, - "height": 0.6, - "depth": 0.015 - }, - "transform": { - "position": { - "x": 0, - "y": 0.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5DC", - "softness": 0.9, - "fluffiness": 0.4, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "macrame-fringe", - "type": "box", - "name": "Cotton Fringe", - "dimensions": { - "width": 0.6, - "height": 0.2, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.09999999999999998, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#f5b2b2", - "softness": 0.88, - "fluffiness": 0.45, - "roughness": 0.88, - "hardness": 0.1, - "specularIntensity": 0, - "specularColor": "#ffffff", - "envMapIntensity": 0.2, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(59%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(93%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0.02, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "textureSoftness": 0.5, - "textureHardness": 0.2, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 89 - }, - "metalness": 0 - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "macrame-cord-l", - "type": "cylinder", - "name": "Hanging Cord Left", - "dimensions": { - "radiusTop": 0.004, - "radiusBottom": 0.004, - "height": 0.4 - }, - "transform": { - "position": { - "x": -0.15, - "y": 0.92, - "z": 0 - }, - "rotation": { - "x": -3.141592653589793, - "y": 0, - "z": 2.141592653589793 - }, - "scale": { - "x": -0.2892663684586106, - "y": 1, - "z": 0.051530339566935784 - } - }, - "material": { - "color": "#E5E4E2", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "macrame-cord-r", - "type": "cylinder", - "name": "Hanging Cord Right", - "dimensions": { - "radiusTop": 0.004, - "radiusBottom": 0.004, - "height": 0.4 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.92, - "z": 0 - }, - "rotation": { - "x": -3.141592653589793, - "y": 0, - "z": -2.141592653589793 - }, - "scale": { - "x": 3.0764768565771505e-06, - "y": 1, - "z": 0.7601096396874588 - } - }, - "material": { - "color": "#E5E4E2", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "boho-macrame-hanging", - "name": "Boho Macrame Wall Hanging", - "children": [ - "macrame-dowel", - "macrame-body", - "macrame-fringe", - "macrame-cord-l", - "macrame-cord-r" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 5.856553202676937, - "y": 1.79, - "z": -2.44 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlrl7zxn-adh4", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.5152472768993592, - "z": 0 - } - }, - { - "id": "998aaee1750c98-19c7441f967", - "title": "Chunky knit wool throw blanket with visible weav...", - "notes": "Chunky knit wool throw blanket with visible weave pattern\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset" - ], - "primitives": [ - { - "id": "wool-throw-base", - "type": "box", - "name": "Throw Base", - "dimensions": { - "width": 1.2, - "height": 0.04, - "depth": 1.6, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.02, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.9, - "metalness": 0, - "fluffiness": 0.8, - "generateTexture": "chunky knit wool weave, thick cream yarn loops", - "uvTransform": { - "repeatX": 4, - "repeatY": 6, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wool-throw-fold", - "type": "box", - "name": "Throw Fold", - "dimensions": { - "width": 1.22, - "height": 0.04, - "depth": 0.6, - "edgeRadius": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.06, - "z": 0.45 - }, - "rotation": { - "x": 0.05, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F5F5F5", - "softness": 0.9, - "metalness": 0, - "fluffiness": 0.8, - "generateTexture": "chunky knit wool weave, thick cream yarn loops", - "uvTransform": { - "repeatX": 4, - "repeatY": 2, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(261%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(295%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wool-throw-fringe-n", - "type": "box", - "name": "Fringe North", - "dimensions": { - "width": 1.15, - "height": 0.01, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": -0.85 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 1, - "metalness": 0, - "opacity": 0.9, - "generateTexture": "wool fringe tassels, vertical strands", - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wool-throw-fringe-s", - "type": "box", - "name": "Fringe South", - "dimensions": { - "width": 1.15, - "height": 0.01, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0.85 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E8E8E8", - "softness": 1, - "metalness": 0, - "opacity": 0.9, - "generateTexture": "wool fringe tassels, vertical strands", - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "chunky-knit-throw-group", - "name": "Chunky Knit Wool Throw", - "children": [ - "wool-throw-base", - "wool-throw-fold", - "wool-throw-fringe-n", - "wool-throw-fringe-s" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.722957697499421, - "y": 0.5995631183491226, - "z": -2.443810500735502 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.319078993031496, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszhahj-2cou", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.04748437812509175, - "z": 0 - } - }, - { - "id": "94d820b5be849-19c7442b3c5", - "title": "Pair of plush white bed pillows with cotton text...", - "notes": "Pair of plush white bed pillows with cotton texture and realistic folds\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "bedroom", - "bedding" - ], - "primitives": [ - { - "id": "pillow-left", - "type": "box", - "name": "Left Pillow", - "dimensions": { - "width": 0.7, - "height": 0.18, - "depth": 0.5, - "edgeRadius": 0.08 - }, - "transform": { - "position": { - "x": -0.38, - "y": 0.11, - "z": 0 - }, - "rotation": { - "x": 0.1, - "y": 0.05, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.9, - "hardness": 0.1, - "fluffiness": 0.7, - "generateTexture": "white cotton fabric with soft organic folds and creases", - "textureSoftness": 0.25, - "textureHardness": 0.5, - "roughness": 0.9, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(334%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(8%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pillow-right", - "type": "box", - "name": "Right Pillow", - "dimensions": { - "width": 0.7, - "height": 0.18, - "depth": 0.5, - "edgeRadius": 0.08 - }, - "transform": { - "position": { - "x": 0.38, - "y": 0.11, - "z": 0 - }, - "rotation": { - "x": 0.1, - "y": -0.05, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 0.9, - "hardness": 0.1, - "fluffiness": 0.7, - "generateTexture": "white cotton fabric with soft organic folds and creases", - "textureSoftness": 0.25, - "textureHardness": 0.5, - "roughness": 0.9, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(334%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(8%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "plush-pillows-pair", - "name": "Pair of Plush Pillows", - "children": [ - "pillow-left", - "pillow-right" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.649719211474874, - "y": 0.7187351739850696, - "z": -2.4659646636791352 - }, - "rotation": { - "x": 1.5707963267948963, - "y": 1.050976584154359, - "z": -1.5707963267948963 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszfm75-nepu", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.11000000000000001, - "z": 0 - } - }, - { - "id": "2668d614a5e848-19c7443bb04", - "title": "Soft fuzzy stuffed teddy bear with button eyes a...", - "notes": "Soft fuzzy stuffed teddy bear with button eyes and ribbon\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "toy", - "bedroom" - ], - "primitives": [ - { - "id": "bear-body", - "type": "sphere", - "name": "Body", - "dimensions": { - "radius": 0.09 - }, - "transform": { - "position": { - "x": 0, - "y": 0.09, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "fluffiness": 0.8, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(359%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(33%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-head", - "type": "sphere", - "name": "Head", - "dimensions": { - "radius": 0.075 - }, - "transform": { - "position": { - "x": 0, - "y": 0.22, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "fluffiness": 0.8, - "roughness": 0.9, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-snout", - "type": "sphere", - "name": "Snout", - "dimensions": { - "radius": 0.025 - }, - "transform": { - "position": { - "x": 0, - "y": 0.21, - "z": 0.06 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-ear-l", - "type": "sphere", - "name": "Left Ear", - "dimensions": { - "radius": 0.025 - }, - "transform": { - "position": { - "x": -0.05, - "y": 0.28, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-ear-r", - "type": "sphere", - "name": "Right Ear", - "dimensions": { - "radius": 0.025 - }, - "transform": { - "position": { - "x": 0.05, - "y": 0.28, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-eye-l", - "type": "sphere", - "name": "Left Eye", - "dimensions": { - "radius": 0.006 - }, - "transform": { - "position": { - "x": -0.025, - "y": 0.23, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.5, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-eye-r", - "type": "sphere", - "name": "Right Eye", - "dimensions": { - "radius": 0.006 - }, - "transform": { - "position": { - "x": 0.025, - "y": 0.23, - "z": 0.065 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "metalness": 0.5, - "softness": 0.1, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-leg-l", - "type": "cylinder", - "name": "Left Leg", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.1 - }, - "transform": { - "position": { - "x": -0.05, - "y": 0.03, - "z": 0.08 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0.2 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-leg-r", - "type": "cylinder", - "name": "Right Leg", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0.05, - "y": 0.03, - "z": 0.08 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": -0.2 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-arm-l", - "type": "cylinder", - "name": "Left Arm", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.09 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.15, - "z": 0.03 - }, - "rotation": { - "x": 0.4, - "y": 0, - "z": 0.5 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-arm-r", - "type": "cylinder", - "name": "Right Arm", - "dimensions": { - "radiusTop": 0.025, - "radiusBottom": 0.025, - "height": 0.09 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.15, - "z": 0.03 - }, - "rotation": { - "x": 0.4, - "y": 0, - "z": -0.5 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8B4513", - "softness": 0.9, - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "bear-ribbon", - "type": "torus", - "name": "Ribbon", - "dimensions": { - "radius": 0.06, - "tube": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.16, - "z": 0 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B22222", - "softness": 0.5, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "stuffed-teddy-bear", - "name": "Stuffed Teddy Bear", - "children": [ - "bear-body", - "bear-head", - "bear-snout", - "bear-ear-l", - "bear-ear-r", - "bear-eye-l", - "bear-eye-r", - "bear-leg-l", - "bear-leg-r", - "bear-arm-l", - "bear-arm-r", - "bear-ribbon" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -0.8510426342848817, - "y": 1.0184118900507562, - "z": -4.745109333958126 - }, - "rotation": { - "x": 0, - "y": -0.35751964509038986, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszls7x-pusm", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.1524781208620488, - "z": 0.022493639105132107 - } - }, - { - "id": "72878a8f5ffb28-19c74452ecd", - "title": "laptop open", - "notes": "Sleek modern silver laptop open with glowing screen and keyboard detail\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [], - "primitives": [ - { - "id": "laptop-chassis-bottom", - "type": "box", - "name": "Laptop Base", - "dimensions": { - "width": 0.35, - "height": 0.01, - "depth": 0.24, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "laptop-keyboard-area", - "type": "box", - "name": "Keyboard", - "dimensions": { - "width": 0.32, - "height": 0.002, - "depth": 0.12 - }, - "transform": { - "position": { - "x": 0, - "y": 0.011, - "z": -0.03 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#1A1A1A", - "softness": 0.4, - "metalness": 0.1, - "generateTexture": "top-down view of a modern laptop keyboard layout, black keys on silver background", - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(162%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(196%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "laptop-trackpad", - "type": "box", - "name": "Trackpad", - "dimensions": { - "width": 0.12, - "height": 0.001, - "depth": 0.07, - "edgeRadius": 0.002 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0105, - "z": 0.075 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B0B0B0", - "softness": 0.2, - "metalness": 0.5, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "laptop-lid-back", - "type": "box", - "name": "Lid Outer", - "dimensions": { - "width": 0.35, - "height": 0.24, - "depth": 0.006, - "edgeRadius": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.12, - "z": -0.16 - }, - "rotation": { - "x": -0.37548927631205564, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#C0C0C0", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "laptop-screen-display", - "type": "box", - "name": "Screen", - "dimensions": { - "width": 0.33, - "height": 0.21, - "depth": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.12, - "z": -0.156 - }, - "rotation": { - "x": -0.38278536055503926, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "emissive": "#A0D0FF", - "emissiveIntensity": 1.5, - "softness": 0.1, - "metalness": 0, - "generateTexture": "vibrant modern computer desktop interface with colorful wallpaper", - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(311%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(345%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "modern-laptop", - "name": "Modern Silver Laptop", - "children": [ - "laptop-chassis-bottom", - "laptop-keyboard-area", - "laptop-trackpad", - "laptop-lid-back", - "laptop-screen-display" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.7, - "y": 0.96, - "z": -0.41 - }, - "rotation": { - "x": 0, - "y": 3.141592653589793, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlsziee8-boq3", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.11673651631330051, - "z": -0.04432948718632643 - } - }, - { - "id": "be0c1bf4a1e43-19c74455b1e", - "title": "Matte ceramic coffee mug with steam and realisti...", - "notes": "Matte ceramic coffee mug with steam and realistic glaze\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Matte ceramic mug with a hollowed center, dark coffee liquid, and soft translucent steam wisps.", - "tags": [ - "staging-asset", - "kitchenware", - "decor" - ], - "primitives": [ - { - "id": "mug-body", - "type": "cylinder", - "name": "Mug Body", - "dimensions": { - "radiusTop": 0.045, - "radiusBottom": 0.045, - "height": 0.1 - }, - "transform": { - "position": { - "x": 0, - "y": 0.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#EAE0D5", - "softness": 0.7, - "metalness": 0, - "clearcoat": 0.2, - "clearcoatRoughness": 0.4, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "id": "mug-interior-cutout", - "type": "cylinder", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.04, - "height": 0.095 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "mug-handle", - "type": "torus", - "name": "Mug Handle", - "dimensions": { - "radius": 0.025, - "tube": 0.007 - }, - "transform": { - "position": { - "x": 0.045, - "y": 0.05, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#EAE0D5", - "softness": 0.7, - "metalness": 0, - "clearcoat": 0.2, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "mug-liquid", - "type": "cylinder", - "name": "Coffee Liquid", - "dimensions": { - "radiusTop": 0.04, - "radiusBottom": 0.04, - "height": 0.005 - }, - "transform": { - "position": { - "x": 0, - "y": 0.085, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2B1B12", - "softness": 0.2, - "metalness": 0.1, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "steam-wisp-1", - "type": "cone", - "name": "Steam Wisp 1", - "dimensions": { - "radius": 0.012, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0.005, - "y": 0.14, - "z": 0.005 - }, - "rotation": { - "x": 0.1, - "y": 0, - "z": 0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "opacity": 0.15, - "softness": 1, - "emissive": "#FFFFFF", - "emissiveIntensity": 0.1, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "steam-wisp-2", - "type": "cone", - "name": "Steam Wisp 2", - "dimensions": { - "radius": 0.01, - "height": 0.06 - }, - "transform": { - "position": { - "x": -0.008, - "y": 0.13, - "z": -0.005 - }, - "rotation": { - "x": -0.1, - "y": 1.5, - "z": -0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "opacity": 0.1, - "softness": 1, - "emissive": "#FFFFFF", - "emissiveIntensity": 0.1, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "ceramic-coffee-mug-group", - "name": "Ceramic Coffee Mug with Steam", - "children": [ - "mug-body", - "mug-handle", - "mug-liquid", - "steam-wisp-1", - "steam-wisp-2" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -2.084297221134241, - "y": 0.9317528639491932, - "z": -0.46062232996302266 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszkt76-qejm", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.01750006346140766, - "y": 0.09077258996382548, - "z": 0 - } - }, - { - "id": "e891daddeb566-19c7445a6c2", - "title": "Hardcover book lying open with paper pages and p...", - "notes": "Hardcover book lying open with paper pages and printed text\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "book", - "decor" - ], - "primitives": [ - { - "id": "book-spine", - "type": "box", - "name": "Book Spine", - "dimensions": { - "width": 0.02, - "height": 0.006, - "depth": 0.22 - }, - "transform": { - "position": { - "x": 0, - "y": 0.003, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2c3e50", - "softness": 0.4, - "metalness": 0.1, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-cover-left", - "type": "box", - "name": "Left Cover", - "dimensions": { - "width": 0.15, - "height": 0.005, - "depth": 0.22 - }, - "transform": { - "position": { - "x": -0.08, - "y": 0.006, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2c3e50", - "softness": 0.4, - "metalness": 0.1, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-cover-right", - "type": "box", - "name": "Right Cover", - "dimensions": { - "width": 0.15, - "height": 0.005, - "depth": 0.22 - }, - "transform": { - "position": { - "x": 0.08, - "y": 0.006, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2c3e50", - "softness": 0.4, - "metalness": 0.1, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-pages-left", - "type": "box", - "name": "Left Page Stack", - "dimensions": { - "width": 0.14, - "height": 0.012, - "depth": 0.21 - }, - "transform": { - "position": { - "x": -0.075, - "y": 0.014, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#fdfdfd", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-pages-right", - "type": "box", - "name": "Right Page Stack", - "dimensions": { - "width": 0.14, - "height": 0.012, - "depth": 0.21 - }, - "transform": { - "position": { - "x": 0.075, - "y": 0.014, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#fdfdfd", - "softness": 0.8, - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-text-left", - "type": "plane", - "name": "Left Page Text", - "dimensions": { - "width": 0.13, - "height": 0.2 - }, - "transform": { - "position": { - "x": -0.075, - "y": 0.021, - "z": 0 - }, - "rotation": { - "x": -1.5708, - "y": 0, - "z": 0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.9, - "generateTexture": "black serif text on white paper, book page layout", - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(215%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(249%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "book-text-right", - "type": "plane", - "name": "Right Page Text", - "dimensions": { - "width": 0.13, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0.075, - "y": 0.021, - "z": 0 - }, - "rotation": { - "x": -1.5708, - "y": 0, - "z": -0.05 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.9, - "generateTexture": "black serif text on white paper, book page layout", - "roughness": 0.9, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(215%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(249%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "open-book-main", - "name": "Open Hardcover Book", - "children": [ - "book-spine", - "book-cover-left", - "book-cover-right", - "book-pages-left", - "book-pages-right", - "book-text-left", - "book-text-right" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 5.107398196850658, - "y": 0.5789292014696342, - "z": 1.379850337825023 - }, - "rotation": { - "x": 0, - "y": -0.7614917198077298, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszj9uz-ofqh", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.010374427221847459, - "z": 0 - } - }, - { - "id": "466b5c32d8ffc8-19c7445fe6d", - "title": "Thin glass and metal smartphone with reflective ...", - "notes": "Thin glass and metal smartphone with reflective screen surface\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Sleek smartphone designed with a metallic frame, a high-gloss glass screen, and a front-facing camera lens.", - "tags": [ - "staging-asset" - ], - "primitives": [ - { - "id": "phone-body", - "type": "box", - "name": "Phone Body", - "dimensions": { - "width": 0.075, - "height": 0.008, - "depth": 0.16, - "edgeRadius": 0.008 - }, - "transform": { - "position": { - "x": 0, - "y": 0.004, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2c2c2c", - "softness": 0.1, - "metalness": 0.9, - "clearcoat": 0.5, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "phone-screen", - "type": "box", - "name": "Phone Screen", - "dimensions": { - "width": 0.071, - "height": 0.001, - "depth": 0.155, - "edgeRadius": 0.006 - }, - "transform": { - "position": { - "x": 0, - "y": 0.008, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#000000", - "softness": 0, - "metalness": 0.2, - "envMapIntensity": 1.5, - "clearcoat": 1, - "roughness": 0, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(87%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(121%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "phone-camera-lens", - "type": "cylinder", - "name": "Front Camera", - "dimensions": { - "radiusTop": 0.002, - "radiusBottom": 0.002, - "height": 0.001 - }, - "transform": { - "position": { - "x": 0, - "y": 0.0085, - "z": -0.07 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.1, - "metalness": 0.8, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": false, - "castShadow": false, - "receiveShadow": false, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "smartphone-group", - "name": "Smartphone", - "children": [ - "phone-body", - "phone-screen", - "phone-camera-lens" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -5.620367368438998, - "y": 0.6507814526590434, - "z": -3.5722425565895612 - }, - "rotation": { - "x": 0, - "y": 0.5836908321898583, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszn3t1-b55o", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.00625, - "z": -7.82310966007671e-10 - } - }, - { - "id": "bfeb41524c65f8-19c74468aea", - "title": "Light oak wooden bed tray with handles and short...", - "notes": "Light oak wooden bed tray with handles and short legs\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset" - ], - "primitives": [ - { - "id": "tray-base", - "type": "box", - "name": "Tray Base", - "dimensions": { - "width": 0.6, - "height": 0.02, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.21, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E3C9A1", - "softness": 0.4, - "metalness": 0, - "generateTexture": "light oak wood grain", - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(148%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(182%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tray-rim-front", - "type": "box", - "name": "Front Rim", - "dimensions": { - "width": 0.6, - "height": 0.04, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.23, - "z": 0.195 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E3C9A1", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tray-rim-back", - "type": "box", - "name": "Back Rim", - "dimensions": { - "width": 0.6, - "height": 0.04, - "depth": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.23, - "z": -0.195 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E3C9A1", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "tray-rim-left", - "type": "box", - "name": "Left Rim", - "dimensions": { - "width": 0.01, - "height": 0.04, - "depth": 0.4 - }, - "transform": { - "position": { - "x": -0.295, - "y": 0.23, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E3C9A1", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.02, - "height": 0.015, - "depth": 0.12 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "tray-rim-right", - "type": "box", - "name": "Right Rim", - "dimensions": { - "width": 0.01, - "height": 0.04, - "depth": 0.4 - }, - "transform": { - "position": { - "x": 0.295, - "y": 0.23, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#E3C9A1", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "cutouts": [ - { - "type": "box", - "dimensions": { - "width": 0.02, - "height": 0.015, - "depth": 0.12 - }, - "transform": { - "position": { - "x": 0, - "y": 0.005, - "z": 0 - } - } - } - ], - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "leg-fl", - "type": "box", - "name": "Leg Front Left", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.03 - }, - "transform": { - "position": { - "x": -0.25, - "y": 0.1, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-fr", - "type": "box", - "name": "Leg Front Right", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.25, - "y": 0.1, - "z": 0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-bl", - "type": "box", - "name": "Leg Back Left", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.03 - }, - "transform": { - "position": { - "x": -0.25, - "y": 0.1, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "leg-br", - "type": "box", - "name": "Leg Back Right", - "dimensions": { - "width": 0.03, - "height": 0.2, - "depth": 0.03 - }, - "transform": { - "position": { - "x": 0.25, - "y": 0.1, - "z": -0.15 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.4, - "metalness": 0, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "oak-bed-tray", - "name": "Oak Bed Tray", - "children": [ - "tray-base", - "tray-rim-front", - "tray-rim-back", - "tray-rim-left", - "tray-rim-right", - "leg-fl", - "leg-fr", - "leg-bl", - "leg-br" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -4.810490635848853, - "y": 0.696442809417525, - "z": -2.773194360540799 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszka3z-2xyb", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.12499999903142453, - "z": 0 - } - }, - { - "id": "bf9beb5fb79a8-19c744766ad", - "title": "Thick quilted duvet neatly folded with soft fabr...", - "notes": "Thick quilted duvet neatly folded with soft fabric wrinkles\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "bedding" - ], - "primitives": [ - { - "id": "duvet-bottom-layer", - "type": "box", - "name": "Duvet Bottom Layer", - "dimensions": { - "width": 1.4, - "height": 0.12, - "depth": 0.7, - "edgeRadius": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.06, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.8, - "metalness": 0, - "fluffiness": 0.5, - "generateTexture": "white quilted cotton fabric with square stitching and soft fabric wrinkles", - "roughness": 0.8, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(264%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(298%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "duvet-top-fold", - "type": "box", - "name": "Duvet Top Fold", - "dimensions": { - "width": 1.4, - "height": 0.1, - "depth": 0.4, - "edgeRadius": 0.04 - }, - "transform": { - "position": { - "x": 0, - "y": 0.17, - "z": 0.15 - }, - "rotation": { - "x": -0.02, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.8, - "metalness": 0, - "fluffiness": 0.5, - "generateTexture": "white quilted cotton fabric with square stitching and soft fabric wrinkles", - "roughness": 0.8, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(264%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(298%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "duvet-fold-curve", - "type": "cylinder", - "name": "Duvet Fold Edge", - "dimensions": { - "radiusTop": 0.11, - "radiusBottom": 0.11, - "height": 1.4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.11, - "z": 0.35 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 1.5708 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.8, - "metalness": 0, - "fluffiness": 0.5, - "roughness": 0.8, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "folded-quilted-duvet", - "name": "Folded Quilted Duvet", - "children": [ - "duvet-bottom-layer", - "duvet-top-fold", - "duvet-fold-curve" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.0909513930543349, - "y": 0.2944852328458841, - "z": -4.680966959464118 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.43288237437984695, - "y": 1, - "z": 0.6923437151644042 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszg6hl-bc0x", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 0.11199358191488266, - "z": 0.055000002682209004 - } - }, - { - "id": "dbefaeda89b1d-19c744ed429", - "title": "Large detailed oak tree with textured bark and l...", - "notes": "Large detailed oak tree with textured bark and lush green foliage\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "staging-asset", - "garden", - "tree" - ], - "primitives": [ - { - "id": "oak-trunk", - "type": "cylinder", - "name": "Oak Trunk", - "dimensions": { - "radiusTop": 0.35, - "radiusBottom": 0.5, - "height": 4 - }, - "transform": { - "position": { - "x": 0, - "y": 2, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4B3621", - "softness": 0.92, - "metalness": 0, - "generateTexture": "rough dark oak bark texture", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/d4679a510dcb.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-branch-1", - "type": "cylinder", - "name": "Main Branch 1", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.2, - "height": 2.5 - }, - "transform": { - "position": { - "x": 0.8, - "y": 3.5, - "z": 0.5 - }, - "rotation": { - "x": 0.5000000000000002, - "y": 2.7755575615628914e-17, - "z": -0.7999999999999999 - }, - "scale": { - "x": 0.9999999999999999, - "y": 0.9999999999999999, - "z": 1 - } - }, - "material": { - "color": "#4B3621", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/d4679a510dcb.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-branch-2", - "type": "cylinder", - "name": "Main Branch 2", - "dimensions": { - "radiusTop": 0.1, - "radiusBottom": 0.2, - "height": 2.5 - }, - "transform": { - "position": { - "x": -0.8, - "y": 3.5, - "z": -0.5 - }, - "rotation": { - "x": 2.641592653589793, - "y": 2.653589793347516e-06, - "z": 2.3415926535897933 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4B3621", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/d4679a510dcb.avif" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-foliage-main", - "type": "sphere", - "name": "Top Foliage", - "dimensions": { - "radius": 1.8 - }, - "transform": { - "position": { - "x": 0, - "y": 5.5, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2a5026", - "softness": 0.92, - "metalness": 0, - "generateTexture": "lush green oak leaf cluster", - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 4, - "repeatY": 3.8, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/9049fe942dc4.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-foliage-left", - "type": "sphere", - "name": "Left Foliage", - "dimensions": { - "radius": 1.4 - }, - "transform": { - "position": { - "x": -1.5, - "y": 4.5, - "z": -0.7999999999999999 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3A6B35", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.6, - "repeatY": 3.5, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/9049fe942dc4.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-foliage-right", - "type": "sphere", - "name": "Right Foliage", - "dimensions": { - "radius": 1.4 - }, - "transform": { - "position": { - "x": 1.6529725665623713, - "y": 4.5, - "z": 0.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6026033030355282, - "y": 0.6026033030355282, - "z": 0.6026033030355282 - } - }, - "material": { - "color": "#2b4e27", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 2.7, - "repeatY": 2.6, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/9049fe942dc4.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - }, - { - "id": "oak-foliage-front", - "type": "sphere", - "name": "Front Foliage", - "dimensions": { - "radius": 1.2 - }, - "transform": { - "position": { - "x": 0.5, - "y": 4.8, - "z": 1.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2D5A27", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.4, - "repeatY": 4.8, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/9049fe942dc4.jpg" - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [], - "_colliderHandle": null - } - ], - "lights": [], - "groups": [ - { - "id": "large-oak-tree", - "name": "Large Oak Tree", - "children": [ - "oak-trunk", - "oak-branch-1", - "oak-branch-2", - "oak-foliage-main", - "oak-foliage-left", - "oak-foliage-right", - "oak-foliage-front" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": -1.5512307374727492, - "y": 2.5134853929878243, - "z": 8.016079845065434 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6996114045873896, - "y": 0.6996114045873896, - "z": 0.6996114045873896 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlszyfgz-jdx6", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0, - "y": 3.649999976158142, - "z": 0.25000003576278695 - } - }, - { - "id": "60e08e06242f38-19c74564e1e", - "title": "Galvanized steel watering can with a long spout ...", - "notes": "Galvanized steel watering can with a long spout and handle\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "reasoning": "Galvanized steel watering can with a tapered body, angled spout, and rose head using metallic materials.", - "tags": [ - "staging-asset", - "garden-tool" - ], - "primitives": [ - { - "id": "wc-body", - "type": "cylinder", - "name": "Main Body", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.1, - "height": 0.25 - }, - "transform": { - "position": { - "x": 0, - "y": 0.125, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9ea1a3", - "metalness": 0.9, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(43%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(77%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wc-spout", - "type": "cylinder", - "name": "Spout", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.018, - "height": 0.35 - }, - "transform": { - "position": { - "x": 0.15, - "y": 0.15, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -1.1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9ea1a3", - "metalness": 0.9, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wc-rose", - "type": "cone", - "name": "Rose Head", - "dimensions": { - "radius": 0.04, - "height": 0.03 - }, - "transform": { - "position": { - "x": 0.32, - "y": 0.28, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": -1.1 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#8a8d8f", - "metalness": 0.8, - "softness": 0.3, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wc-handle-top", - "type": "torus", - "name": "Top Handle", - "dimensions": { - "radius": 0.08, - "tube": 0.01 - }, - "transform": { - "position": { - "x": 0, - "y": 0.25, - "z": 0 - }, - "rotation": { - "x": 1.57, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9ea1a3", - "metalness": 0.9, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "wc-handle-back", - "type": "cylinder", - "name": "Back Handle", - "dimensions": { - "radiusTop": 0.01, - "radiusBottom": 0.01, - "height": 0.2 - }, - "transform": { - "position": { - "x": -0.12, - "y": 0.15, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0.3 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#9ea1a3", - "metalness": 0.9, - "softness": 0.2, - "roughness": 0.2, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "physics": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "watering-can-group", - "name": "Watering Can", - "children": [ - "wc-body", - "wc-spout", - "wc-rose", - "wc-handle-top", - "wc-handle-back" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 5.241405996940184, - "y": 0.21187023344930833, - "z": 5.203786043018361 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "pickable": true, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlt04otc-2jg0", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.09620328438469122, - "y": 0.16122611763747985, - "z": 0 - } - }, - { - "id": "843c6307ab323-19c7456cda8", - "title": "Large terracotta garden pots with intricate flor...", - "notes": "Large terracotta garden pots with intricate floral relief patterns\n\nIMPORTANT: Generate exactly ONE asset only. Do not include other props or scene layout.", - "states": [ - { - "id": "state-default", - "name": "default", - "scene": { - "tags": [ - "terracotta", - "garden", - "pot", - "floral" - ], - "primitives": [ - { - "id": "pot-large-body", - "type": "cylinder", - "name": "Large Pot Body", - "dimensions": { - "radiusTop": 0.35, - "radiusBottom": 0.25, - "height": 0.6 - }, - "transform": { - "position": { - "x": 0, - "y": 0.3, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B66A50", - "softness": 0.8, - "generateTexture": "intricate weathered terracotta with embossed floral relief patterns", - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%20512%20512'%3E%0A%3Cdefs%3E%0A%20%20%3ClinearGradient%20id%3D'g'%20x1%3D'0'%20y1%3D'0'%20x2%3D'1'%20y2%3D'1'%3E%0A%20%20%20%20%3Cstop%20offset%3D'0%25'%20stop-color%3D'hsl(320%2C22%25%2C42%25)'%2F%3E%0A%20%20%20%20%3Cstop%20offset%3D'100%25'%20stop-color%3D'hsl(354%2C24%25%2C55%25)'%2F%3E%0A%20%20%3C%2FlinearGradient%3E%0A%20%20%3Cpattern%20id%3D'p'%20width%3D'32'%20height%3D'32'%20patternUnits%3D'userSpaceOnUse'%3E%0A%20%20%20%20%3Crect%20width%3D'32'%20height%3D'32'%20fill%3D'none'%2F%3E%0A%20%20%20%20%3Cpath%20d%3D'M0%2031%20L31%200'%20stroke%3D'rgba(255%2C255%2C255%2C0.14)'%20stroke-width%3D'1'%2F%3E%0A%20%20%3C%2Fpattern%3E%0A%3C%2Fdefs%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23g)'%2F%3E%0A%3Crect%20width%3D'512'%20height%3D'512'%20fill%3D'url(%23p)'%2F%3E%0A%3C%2Fsvg%3E", - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-large-rim", - "type": "cylinder", - "name": "Large Pot Rim", - "dimensions": { - "radiusTop": 0.38, - "radiusBottom": 0.38, - "height": 0.08 - }, - "transform": { - "position": { - "x": 0, - "y": 0.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B66A50", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-large-soil", - "type": "cylinder", - "name": "Large Pot Soil", - "dimensions": { - "radiusTop": 0.33, - "radiusBottom": 0.33, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 0.58, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3B2F2F", - "softness": 1, - "fluffiness": 0.5, - "roughness": 1, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "receiveShadow": true, - "physics": true, - "castShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-med-body", - "type": "cylinder", - "name": "Medium Pot Body", - "dimensions": { - "radiusTop": 0.25, - "radiusBottom": 0.18, - "height": 0.45 - }, - "transform": { - "position": { - "x": 0.75, - "y": 0.225, - "z": -0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B66A50", - "softness": 0.8, - "generateTexture": "weathered terracotta with ornate floral vine relief carvings", - "roughness": 0.8, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-med-rim", - "type": "cylinder", - "name": "Medium Pot Rim", - "dimensions": { - "radiusTop": 0.28, - "radiusBottom": 0.28, - "height": 0.06 - }, - "transform": { - "position": { - "x": 0.75, - "y": 0.45, - "z": -0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#B66A50", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": true, - "receiveShadow": true, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - }, - { - "id": "pot-med-soil", - "type": "cylinder", - "name": "Medium Pot Soil", - "dimensions": { - "radiusTop": 0.23, - "radiusBottom": 0.23, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0.75, - "y": 0.43, - "z": -0.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#3B2F2F", - "softness": 1, - "fluffiness": 0.5, - "roughness": 1, - "hardness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "receiveShadow": true, - "physics": true, - "castShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "cutouts": [] - } - ], - "lights": [], - "groups": [ - { - "id": "terracotta-pots-set", - "name": "Terracotta Garden Pots", - "children": [ - "pot-large-body", - "pot-large-rim", - "pot-large-soil", - "pot-med-body", - "pot-med-rim", - "pot-med-soil" - ] - } - ] - }, - "interactions": [] - } - ], - "currentStateId": "state-default", - "actions": [], - "transform": { - "position": { - "x": 5.795020447146578, - "y": 0.22466053141207248, - "z": 8.532342244184631 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.5624222520343933, - "y": 0.5624222520343933, - "z": 0.5624222520343933 - } - }, - "pickable": false, - "bumpable": false, - "bumpResponse": 0.9, - "bumpDamping": 0.9, - "libraryAssetId": "stg-mlt03czz-pyxf", - "castShadow": false, - "receiveShadow": false, - "_shapePivotCenter": { - "x": 0.32500000298023224, - "y": 0.31999999359250064, - "z": -2.980232227667301e-09 - } - } -]; diff --git a/misc/DimSim/scenes/apartment/data/sky.js b/misc/DimSim/scenes/apartment/data/sky.js deleted file mode 100644 index 32f5a2f5dd..0000000000 --- a/misc/DimSim/scenes/apartment/data/sky.js +++ /dev/null @@ -1,11 +0,0 @@ -// Sky / atmosphere settings. -export const SKY = { - "enabled": true, - "topColor": "#0b64f4", - "horizonColor": "#d9e5f7", - "bottomColor": "#3b6cce", - "brightness": 0.9, - "softness": 0.75, - "sunStrength": 0.17, - "sunHeight": 0.34 -}; diff --git a/misc/DimSim/scenes/apartment/data/structure.js b/misc/DimSim/scenes/apartment/data/structure.js deleted file mode 100644 index 3759245b36..0000000000 --- a/misc/DimSim/scenes/apartment/data/structure.js +++ /dev/null @@ -1,5664 +0,0 @@ -// Static geometry (walls, floor, ceiling, fixtures). -export const PRIMITIVES = [ - { - "id": "apartment-floor", - "type": "box", - "name": "Apartment Floor Slab", - "dimensions": { - "width": 12, - "height": 0.1, - "depth": 10 - }, - "transform": { - "position": { - "x": 0, - "y": 0.00813020206586168, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#D2B48C", - "softness": 0.4, - "metalness": 0.1, - "roughness": 0.4, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "front-yard-floor", - "type": "box", - "name": "Front Yard Grass", - "dimensions": { - "width": 12, - "height": 0.05, - "depth": 4 - }, - "transform": { - "position": { - "x": 0, - "y": 0.025, - "z": 7 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#4F7942", - "softness": 0.92, - "metalness": 0, - "roughness": 0.92, - "hardness": 0.12, - "fluffiness": 0.72, - "specularIntensity": 0.25, - "specularColor": "#ffffff", - "envMapIntensity": 0.18, - "opacity": 1, - "transmission": 0, - "ior": 1.4, - "thickness": 0.04, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0.95, - "textureSoftness": 0.7, - "textureHardness": 0.18, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 4.4, - "repeatY": 1.1, - "offsetX": 0.23, - "offsetY": 0, - "rotationDeg": -7 - }, - "texturePath": "textures/dc34e383b17a.jpg" - }, - "physics": true, - "castShadow": false, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "yard-wall-south", - "type": "box", - "name": "Yard Wall South", - "dimensions": { - "width": 12.4, - "height": 0.5, - "depth": 0.2 - }, - "transform": { - "position": { - "x": 0, - "y": 0.25, - "z": 9.1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#808080", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "yard-wall-west", - "type": "box", - "name": "Yard Wall West", - "dimensions": { - "width": 0.2, - "height": 0.5, - "depth": 4.2 - }, - "transform": { - "position": { - "x": -6.1, - "y": 0.25, - "z": 7 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#808080", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "yard-wall-east", - "type": "box", - "name": "Yard Wall East", - "dimensions": { - "width": 0.2, - "height": 0.5, - "depth": 4.2 - }, - "transform": { - "position": { - "x": 6.1, - "y": 0.25, - "z": 7 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#808080", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-north", - "type": "box", - "name": "North Exterior Wall", - "dimensions": { - "width": 12, - "height": 3, - "depth": 0.2 - }, - "transform": { - "position": { - "x": 0, - "y": 1.6, - "z": -5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-east", - "type": "box", - "name": "East Exterior Wall", - "dimensions": { - "width": 0.2, - "height": 3, - "depth": 10 - }, - "transform": { - "position": { - "x": 6, - "y": 1.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#0d0d0d", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-west", - "type": "box", - "name": "West Exterior Wall", - "dimensions": { - "width": 0.2, - "height": 3, - "depth": 10 - }, - "transform": { - "position": { - "x": -6, - "y": 1.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#5e77b0", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-south-seg-1", - "type": "box", - "name": "South Wall Segment Left", - "dimensions": { - "width": 1.5, - "height": 3, - "depth": 0.2 - }, - "transform": { - "position": { - "x": -5.25, - "y": 1.6, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ffffff", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-south-seg-2", - "type": "box", - "name": "South Wall Segment Middle", - "dimensions": { - "width": 4.5, - "height": 3, - "depth": 0.2 - }, - "transform": { - "position": { - "x": -1.25, - "y": 1.6, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-south-seg-3", - "type": "box", - "name": "South Wall Segment Right", - "dimensions": { - "width": 3, - "height": 3, - "depth": 0.2 - }, - "transform": { - "position": { - "x": 4.5, - "y": 1.6, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-south-header-entrance", - "type": "box", - "name": "South Wall Header Entrance", - "dimensions": { - "width": 1, - "height": 0.9, - "depth": 0.2 - }, - "transform": { - "position": { - "x": -4, - "y": 2.7, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-south-header-sliding", - "type": "box", - "name": "South Wall Header Sliding Door", - "dimensions": { - "width": 2, - "height": 0.9, - "depth": 0.2 - }, - "transform": { - "position": { - "x": 2, - "y": 2.7, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-main-left", - "type": "box", - "name": "Main Divider Left", - "dimensions": { - "width": 5.5, - "height": 3, - "depth": 0.15 - }, - "transform": { - "position": { - "x": -3.25, - "y": 1.6, - "z": 0.012688827212309017 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-main-right", - "type": "box", - "name": "Main Divider Right", - "dimensions": { - "width": 5.5, - "height": 3, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 3.25, - "y": 1.6, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#cacac4", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-main-header", - "type": "box", - "name": "Main Divider Header", - "dimensions": { - "width": 1, - "height": 0.9, - "depth": 0.15 - }, - "transform": { - "position": { - "x": 0, - "y": 2.55, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-kitchen-back", - "type": "box", - "name": "Kitchen Divider Back", - "dimensions": { - "width": 0.15, - "height": 3, - "depth": 2 - }, - "transform": { - "position": { - "x": -2, - "y": 1.6, - "z": 1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-kitchen-front", - "type": "box", - "name": "Kitchen Divider Front", - "dimensions": { - "width": 0.15, - "height": 3, - "depth": 2 - }, - "transform": { - "position": { - "x": -2, - "y": 1.6, - "z": 4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-kitchen-header", - "type": "box", - "name": "Kitchen Divider Header", - "dimensions": { - "width": 0.15, - "height": 0.9, - "depth": 1 - }, - "transform": { - "position": { - "x": -2, - "y": 2.55, - "z": 2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-bathroom-back", - "type": "box", - "name": "Bathroom Divider Back", - "dimensions": { - "width": 0.15, - "height": 3, - "depth": 2 - }, - "transform": { - "position": { - "x": 1, - "y": 1.6, - "z": -4 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-bathroom-front", - "type": "box", - "name": "Bathroom Divider Front", - "dimensions": { - "width": 0.15, - "height": 3, - "depth": 2 - }, - "transform": { - "position": { - "x": 1, - "y": 1.5434592146356212, - "z": -1 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "wall-bathroom-header", - "type": "box", - "name": "Bathroom Divider Header", - "dimensions": { - "width": 0.15, - "height": 0.9, - "depth": 1 - }, - "transform": { - "position": { - "x": 1, - "y": 2.55, - "z": -2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#F8F8F8", - "softness": 0.7, - "metalness": 0, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "ceiling-slab", - "type": "box", - "name": "Ceiling Slab", - "dimensions": { - "width": 12, - "height": 0.2, - "depth": 10 - }, - "transform": { - "position": { - "x": 0, - "y": 3.1, - "z": 0 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#FFFFFF", - "softness": 1, - "roughness": 1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "downlight-cutout-1", - "type": "cylinder", - "name": "Downlight Cutout LR", - "dimensions": { - "radiusTop": 0.075, - "radiusBottom": 0.075, - "height": 0.02 - }, - "transform": { - "position": { - "x": 3, - "y": 3, - "z": 2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "downlight-cutout-2", - "type": "cylinder", - "name": "Downlight Cutout Kitchen", - "dimensions": { - "radiusTop": 0.075, - "radiusBottom": 0.075, - "height": 0.02 - }, - "transform": { - "position": { - "x": -3, - "y": 3, - "z": 2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "downlight-cutout-3", - "type": "cylinder", - "name": "Downlight Cutout Bedroom", - "dimensions": { - "radiusTop": 0.075, - "radiusBottom": 0.075, - "height": 0.02 - }, - "transform": { - "position": { - "x": 3, - "y": 3, - "z": -2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "downlight-cutout-4", - "type": "cylinder", - "name": "Downlight Cutout Bathroom", - "dimensions": { - "radiusTop": 0.075, - "radiusBottom": 0.075, - "height": 0.02 - }, - "transform": { - "position": { - "x": -3, - "y": 3, - "z": -2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-south-frame-top", - "type": "box", - "name": "Window Frame Top", - "dimensions": { - "width": 2, - "height": 0.05, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 2, - "y": 2.225, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-south-frame-bottom", - "type": "box", - "name": "Window Frame Bottom", - "dimensions": { - "width": 2, - "height": 0.05, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 2, - "y": 0.11827884470220063, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-south-frame-left", - "type": "box", - "name": "Window Frame Left", - "dimensions": { - "width": 0.05, - "height": 1.4, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 1.025, - "y": 1.5, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 2.026631513257506, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-south-frame-right", - "type": "box", - "name": "Window Frame Right", - "dimensions": { - "width": 0.05, - "height": 1.4, - "depth": 0.1 - }, - "transform": { - "position": { - "x": 2.975, - "y": 1.5, - "z": 5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 2.057602018219857, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-south-glass", - "type": "box", - "name": "Window Glass", - "dimensions": { - "width": 1.9, - "height": 1.4, - "depth": 0.02 - }, - "transform": { - "position": { - "x": 2, - "y": 1.498572723371466, - "z": 5.021528258120427 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 2.061706354585481, - "z": 1 - } - }, - "material": { - "color": "#ADD8E6", - "opacity": 0.3, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-east-frame-top", - "type": "box", - "name": "Window Frame Top", - "dimensions": { - "width": 0.1, - "height": 0.05, - "depth": 2 - }, - "transform": { - "position": { - "x": 6, - "y": 2.225, - "z": -2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-east-frame-bottom", - "type": "box", - "name": "Window Frame Bottom", - "dimensions": { - "width": 0.1, - "height": 0.05, - "depth": 2 - }, - "transform": { - "position": { - "x": 6, - "y": 0.775, - "z": -2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-east-frame-left", - "type": "box", - "name": "Window Frame Left", - "dimensions": { - "width": 0.1, - "height": 1.4, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 6, - "y": 1.5, - "z": -2.975 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-east-frame-right", - "type": "box", - "name": "Window Frame Right", - "dimensions": { - "width": 0.1, - "height": 1.4, - "depth": 0.05 - }, - "transform": { - "position": { - "x": 6, - "y": 1.5, - "z": -1.025 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#2C2C2C", - "softness": 0.3, - "metalness": 0.7, - "roughness": 0.3, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "window-east-glass", - "type": "box", - "name": "Window Glass", - "dimensions": { - "width": 0.02, - "height": 1.4, - "depth": 1.9 - }, - "transform": { - "position": { - "x": 6, - "y": 1.5, - "z": -2 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#ADD8E6", - "opacity": 0.3, - "transmission": 0.9, - "ior": 1.5, - "softness": 0.7, - "roughness": 0.7, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "lr-light-trim-1", - "type": "cylinder", - "name": "LR Light Trim 1", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 0, - "y": 3, - "z": 1.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "lr-light-trim-2", - "type": "cylinder", - "name": "LR Light Trim 2", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 4, - "y": 3, - "z": 1.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "lr-light-trim-4", - "type": "cylinder", - "name": "LR Light Trim 4", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 4, - "y": 3, - "z": 3.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "kitchen-light-trim-1", - "type": "cylinder", - "name": "Kitchen Light Trim 1", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": -4, - "y": 3, - "z": 1.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "kitchen-light-trim-2", - "type": "cylinder", - "name": "Kitchen Light Trim 2", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": -4, - "y": 3, - "z": 3.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "bedroom-light-trim-1", - "type": "cylinder", - "name": "Bedroom Light Trim 1", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": -2.5, - "y": 3, - "z": -1.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "bedroom-light-trim-2", - "type": "cylinder", - "name": "Bedroom Light Trim 2", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": -2.5, - "y": 3, - "z": -3.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "bathroom-light-trim-1", - "type": "cylinder", - "name": "Bathroom Light Trim 1", - "dimensions": { - "radiusTop": 0.08, - "radiusBottom": 0.08, - "height": 0.02 - }, - "transform": { - "position": { - "x": 3.5, - "y": 3, - "z": -2.5 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#111111", - "softness": 0.5, - "metalness": 0.2, - "roughness": 0.5, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "castShadow": false, - "receiveShadow": false, - "physics": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "path-light-base-1", - "type": "cylinder", - "name": "Path Light Base 1", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.2 - }, - "transform": { - "position": { - "x": -2, - "y": 0.1, - "z": 5.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "path-light-base-2", - "type": "cylinder", - "name": "Path Light Base 2", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.2 - }, - "transform": { - "position": { - "x": 0, - "y": 0.1, - "z": 5.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "path-light-base-3", - "type": "cylinder", - "name": "Path Light Base 3", - "dimensions": { - "radiusTop": 0.03, - "radiusBottom": 0.03, - "height": 0.2 - }, - "transform": { - "position": { - "x": 2, - "y": 0.1, - "z": 5.8 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1, - "y": 1, - "z": 1 - } - }, - "material": { - "color": "#333333", - "softness": 0.1, - "metalness": 0.9, - "roughness": 0.1, - "hardness": 0, - "fluffiness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "textureDataUrl": null, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "alphaCutoff": 0, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - } - }, - "physics": true, - "castShadow": true, - "receiveShadow": true, - "notes": "", - "tags": [], - "state": "static", - "metadata": {} - }, - { - "id": "783be8417c9b68-19c6daa8d28", - "type": "plane", - "name": "Wooden Floor", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 1.9499811797054045, - "y": 0.11479516914077526, - "z": 2.5224651049954567 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 3.973075253030828, - "y": 2.505288504802451, - "z": 19.684670163409347 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/faada43680d3.jpg" - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "8335d17d79d7d-19c72918c44", - "type": "plane", - "name": "Plane copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -2.548736547541645, - "y": 0.09589921676402557, - "z": -2.462454631636581 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 3.5707846155541514, - "y": 2.505288504802451, - "z": 19.684670163409347 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/faada43680d3.jpg" - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "04f41436813cd-19c7293d8e6", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 3.3425399746150166, - "y": 0.08145220992406443, - "z": -2.270485912724944 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 2.7523616637617936, - "y": 2.7523616637617936, - "z": 2.7523616637617936 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 5.1, - "repeatY": 5.3, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/08e5e2b877e1.jpg" - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "c087837b444638-19c7296d766", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -3.7303788186939344, - "y": 0.08969239741599833, - "z": 2.5666047855931198 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.7457717771446934, - "y": 2.5261863196647547, - "z": 2.8705851724882394 - } - }, - "material": { - "color": "#808080", - "roughness": 0.96, - "softness": 0.96, - "hardness": 0.88, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 0.2, - "specularColor": "#ffffff", - "envMapIntensity": 0.15, - "opacity": 1, - "transmission": 0, - "ior": 1.5, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 1, - "alphaCutoff": 0, - "textureSoftness": 0.35, - "textureHardness": 0.7, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 3.5, - "repeatY": 5.9, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "texturePath": "textures/af07500a616c.webp" - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "0379edf13da0a-19c72992dab", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -3.99963558334846, - "y": 0.05741370387386052, - "z": 5.032630876866396 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.0307684092832463, - "y": 0.07524662771800945, - "z": 0.1327083917447404 - } - }, - "material": { - "color": "#747272", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "ea4192c9414a48-19c730f2895", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 5.87425878966027, - "y": 1.5871495527977475, - "z": 2.4898086797049364 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 2.4340540365918795, - "y": 1.4798606990825292, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "f943a66bf590a8-19c737557be", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.281145415825732, - "y": 0.571374473094583, - "z": 2.7080407264295236 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.02118022478582253, - "y": 0.7608388096193667, - "z": 1 - } - }, - "material": { - "color": "#5c5c5c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "7b36be69ab5f9-19c73779a39", - "type": "box", - "name": "counter top", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.592825880628662, - "y": 0.9629473017917998, - "z": 3.979374310570936 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6415432111938124, - "y": 0.030534569005993762, - "z": 1.8728951285475166 - } - }, - "material": { - "color": "#222222", - "roughness": 0.9, - "softness": 0.9, - "hardness": 0.58, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 0.4, - "specularColor": "#ffffff", - "envMapIntensity": 0.25, - "opacity": 1, - "transmission": 0, - "ior": 1.52, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.85, - "clearcoatRoughness": 0.19999999999999996, - "alphaCutoff": 0, - "textureSoftness": 0.55, - "textureHardness": 0.25, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "1edfcf9d820c38-19c737b3dea", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.601804712793383, - "y": 0.1597592283515767, - "z": 2.70050519498599 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6104781025633932, - "y": 0.06677847501178394, - "z": 1 - } - }, - "material": { - "color": "#222222", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "300c8b5b9d2268-19c737cddea", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.599990138028935, - "y": 0.5709156037037538, - "z": 0.7005983724176564 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6104781025633932, - "y": 0.7765067159413703, - "z": 1.3006021507278882 - } - }, - "material": { - "color": "#5c5c5c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "57e0f28d450758-19c737da478", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.602253941815052, - "y": 0.1597592283515767, - "z": 1.6632802784622847 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6104781025633932, - "y": 0.06677847501178394, - "z": 2.9846531143176267 - } - }, - "material": { - "color": "#222222", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "dc03685ba8afc-19c737eac11", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.28004311005808, - "y": 0.5709156037037538, - "z": 2.9376932413135677 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.031910310947402244, - "y": 0.7320533043664592, - "z": 0.42713125248605477 - } - }, - "material": { - "color": "#5c5c5c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "a70fbe76145d98-19c737f8136", - "type": "box", - "name": "Box copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.275241230716765, - "y": 0.5709156037037538, - "z": 2.4463799300425264 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.031910310947402244, - "y": 0.7320533043664592, - "z": 0.42713125248605477 - } - }, - "material": { - "color": "#5c5c5c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "d5ac39c69ea1f-19c738008ac", - "type": "box", - "name": "Box", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.257358385471168, - "y": 0.5912214945385093, - "z": 2.7706532825233054 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.01, - "y": 0.01, - "z": 0.52 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "0b93f0188a3a98-19c738106dc", - "type": "box", - "name": "Box copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.258383417198247, - "y": 0.5868688079397046, - "z": 2.5937656709903236 - }, - "rotation": { - "x": 1.5707963267948966, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.01, - "y": 0.01, - "z": 0.52 - } - }, - "material": { - "color": "#808080", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "10258e2601f01-19c73b500a9", - "type": "box", - "name": "counter top copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 1, - "height": 1, - "depth": 1, - "edgeRadius": 0, - "edgeSegments": 4, - "widthSegments": 1, - "heightSegments": 1, - "depthSegments": 1 - }, - "transform": { - "position": { - "x": -5.590946496428486, - "y": 0.9629473017917998, - "z": 1.1978787402098119 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.6415432111938124, - "y": 0.030534569005993762, - "z": 2.2517642186060898 - } - }, - "material": { - "color": "#222222", - "roughness": 0.9, - "softness": 0.9, - "hardness": 0.58, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 0.4, - "specularColor": "#ffffff", - "envMapIntensity": 0.25, - "opacity": 1, - "transmission": 0, - "ior": 1.52, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0.85, - "clearcoatRoughness": 0.19999999999999996, - "alphaCutoff": 0, - "textureSoftness": 0.55, - "textureHardness": 0.25, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": true, - "receiveShadow": true - }, - { - "id": "757f5b99740bc8-19c7421407d", - "type": "plane", - "name": "Plane", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -5.896691185276851, - "y": 1.9002281170120718, - "z": 2.497801452904678 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 2.4067091733612673, - "y": 1.105765175939889, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "6529c964ed9c48-19c742206f5", - "type": "plane", - "name": "Plane copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -3.9945150774550386, - "y": 1.548538143174976, - "z": 0.09109105470202561 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.9150984716058357, - "y": 1.6501678483888989, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "3cdf3c74e21738-19c7423fa90", - "type": "plane", - "name": "Plane copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -2.0866166516533178, - "y": 1.55, - "z": 0.9879211662233722 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.0098416828841905, - "y": 1.65, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "e211a7cfba6098-19c742616ce", - "type": "plane", - "name": "Plane copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -2.079468429661268, - "y": 1.55, - "z": 4.008907836090804 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.0098416828841905, - "y": 1.65, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "3832ba4a663b18-19c74266e2c", - "type": "plane", - "name": "Plane copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -2.0905822043840847, - "y": 2.667278164723253, - "z": 2.7832702590785026 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 1.0098416828841905, - "y": 0.5635515294365225, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "7f1aa5007dc09-19c74275609", - "type": "plane", - "name": "Plane copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -2.732367750607517, - "y": 1.55, - "z": 4.897304524794526 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.7687618378437209, - "y": 1.65, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "15c49fe32fa1c-19c74288cdb", - "type": "plane", - "name": "Plane copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -5.268666684592633, - "y": 1.55, - "z": 4.897304524794526 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.7687618378437209, - "y": 1.65, - "z": 1 - } - }, - "material": { - "color": "#0c122c", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "2a7cdd6f9d04f-19c74290c86", - "type": "plane", - "name": "Plane copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -1.266158206687444, - "y": 1.633247104283046, - "z": 5.100329993926027 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 2.2343213628098764, - "y": 1.545936740677122, - "z": 1.556053916200829 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "1e6229faca67f8-19c742b9800", - "type": "plane", - "name": "Plane copy copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 4.519250048552278, - "y": 1.633247104283046, - "z": 5.103878449570321 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.5092307395484803, - "y": 1.545936740677122, - "z": 1.556053916200829 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "1b661a4c44dd48-19c742c52f8", - "type": "plane", - "name": "Plane copy copy copy copy copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 2.178431996727124, - "y": 2.723672602796335, - "z": 5.103878449570321 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 1.5092307395484803, - "y": 0.44396423456135964, - "z": 1.556053916200829 - } - }, - "material": { - "color": "#ffffff", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "634e0ced21dc88-19c83a32fb2", - "type": "plane", - "name": "Plane copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": 3.1865602637106494, - "y": 1.59, - "z": 0.08072901728527726 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 2.685299750697142, - "y": 1.48, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "dbc3c36393b35-19c83a4558e", - "type": "plane", - "name": "Plane copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -1.2201811715191955, - "y": 1.593493435339756, - "z": 0.09116940361175385 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.7227000672566991, - "y": 1.48, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "ec87b8c8dfa178-19c83a50983", - "type": "plane", - "name": "Plane copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -0.16284675486099442, - "y": 2.672862262421784, - "z": 0.09116940361175385 - }, - "rotation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": { - "x": 0.7227000672566991, - "y": 0.567054231994336, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "5bb11b4c6365a8-19c83a61793", - "type": "plane", - "name": "Plane copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -1.91726728103272, - "y": 1.59, - "z": 1.0092475796780658 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.9938495526728357, - "y": 1.48, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "bbac7ab73352e8-19c83a727f7", - "type": "plane", - "name": "Plane copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -1.922284295625794, - "y": 1.59, - "z": 3.9969772404633046 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.9938495526728357, - "y": 1.48, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - }, - { - "id": "fc2c976a67628-19c83a7bab9", - "type": "plane", - "name": "Plane copy copy copy copy", - "notes": "", - "tags": [], - "state": "static", - "metadata": {}, - "dimensions": { - "width": 2, - "height": 2, - "widthSegments": 1, - "heightSegments": 1 - }, - "transform": { - "position": { - "x": -1.9085894055370365, - "y": 2.627961007913602, - "z": 2.678720608499149 - }, - "rotation": { - "x": 0, - "y": 1.5707963267948966, - "z": 0 - }, - "scale": { - "x": 0.9938495526728357, - "y": 0.5209561440322126, - "z": 1 - } - }, - "material": { - "color": "#48460e", - "roughness": 0.7, - "softness": 0.7, - "hardness": 0, - "fluffiness": 0, - "metalness": 0, - "specularIntensity": 1, - "specularColor": "#ffffff", - "envMapIntensity": 1, - "opacity": 1, - "transmission": 0, - "ior": 1.45, - "thickness": 0, - "attenuationColor": "#ffffff", - "attenuationDistance": 1, - "iridescence": 0, - "emissive": "#000000", - "emissiveIntensity": 0, - "clearcoat": 0, - "clearcoatRoughness": 0, - "alphaCutoff": 0, - "textureSoftness": 0.25, - "textureHardness": 0.5, - "doubleSided": true, - "flatShading": false, - "wireframe": false, - "uvTransform": { - "repeatX": 1, - "repeatY": 1, - "offsetX": 0, - "offsetY": 0, - "rotationDeg": 0 - }, - "textureDataUrl": null - }, - "physics": true, - "castShadow": false, - "receiveShadow": true - } -]; diff --git a/misc/DimSim/scenes/apartment/data/tags.js b/misc/DimSim/scenes/apartment/data/tags.js deleted file mode 100644 index 98452143dd..0000000000 --- a/misc/DimSim/scenes/apartment/data/tags.js +++ /dev/null @@ -1,7 +0,0 @@ -// World tag list (string filter tags). -export const TAGS = [ - "modern", - "apartment", - "interior", - "furnished" -]; diff --git a/misc/DimSim/scenes/apartment/index.js b/misc/DimSim/scenes/apartment/index.js index 791921941d..f940866399 100644 --- a/misc/DimSim/scenes/apartment/index.js +++ b/misc/DimSim/scenes/apartment/index.js @@ -1,20 +1,118 @@ -import { SKY } from './data/sky.js'; -import { TAGS } from './data/tags.js'; -import { GROUPS } from './data/groups.js'; -import { LIGHTS } from './data/lights.js'; -import { PRIMITIVES } from './data/structure.js'; -import { ASSETS } from './data/objects.js'; - -export default async function build({ loadLevel }) { +// scenes/apartment/index.js — pure Three.js entry for the apartment. +// +// Static structure (walls, floor, ceiling, fixtures) lives in ./structure.glb, +// baked once from the original primitives via cli/export-structure.ts. +// Interactive objects (fridge, cabinets, TV, etc.) live as GLB-per-state files +// under ./objects/, generated by cli/export-apartment.ts. Sky, lights, and tags +// are inlined here so the scene is fully imperative — no data/ files needed. + +const SKY = { + topColor: '#0b64f4', + horizonColor: '#d9e5f7', + bottomColor: '#3b6cce', + brightness: 0.9, + softness: 0.75, + sunStrength: 0.17, + sunHeight: 0.34, +}; + +const TAGS = ['modern', 'apartment', 'interior', 'furnished']; + +export default async function build({ scene, THREE, physics, setSky, loadGLTF, loadLevel }) { + setSky(SKY); + + // ── Lights ───────────────────────────────────────────────────────────────── + const lr1 = new THREE.PointLight(new THREE.Color('#FFFAF0'), 2, 8); + lr1.position.set(2, 2.8, 2.5); + scene.add(lr1); + + const lr2 = new THREE.PointLight(new THREE.Color('#FFFAF0'), 0, 8); + lr2.position.set(4, 2.9, 1.5); + scene.add(lr2); + + const kit1 = new THREE.PointLight(new THREE.Color('#F0FFFF'), 2.5, 8); + kit1.position.set(-4, 2.8, 2.5); + scene.add(kit1); + + const kit2 = new THREE.PointLight(new THREE.Color('#F0FFFF'), 2.5, 8); + kit2.position.set(-4, 2.9, 3.5); + scene.add(kit2); + + const bed1 = new THREE.PointLight(new THREE.Color('#FFE4B5'), 2, 8); + bed1.position.set(-2.5, 2.8, -2.5); + scene.add(bed1); + + const bed2 = new THREE.PointLight(new THREE.Color('#FFE4B5'), 1.5, 8); + bed2.position.set(-2.5, 2.9, -3.5); + scene.add(bed2); + + const path1 = new THREE.PointLight(new THREE.Color('#FFD700'), 1, 3); + path1.position.set(-2, 0.25, 5.8); + scene.add(path1); + + const path2 = new THREE.PointLight(new THREE.Color('#FFD700'), 1, 3); + path2.position.set(0, 0.25, 5.8); + scene.add(path2); + + const path3 = new THREE.PointLight(new THREE.Color('#FFD700'), 1, 3); + path3.position.set(2, 0.25, 5.8); + scene.add(path3); + + const bath1 = new THREE.PointLight(new THREE.Color('#F0FFFF'), 1.5, 6); + bath1.position.set(3.5, 2.8, -2.5); + scene.add(bath1); + + const sun = new THREE.DirectionalLight(new THREE.Color('#ffffff'), 1); + sun.position.set(2.9245321131985382, 14.007232336425105, 16.10510431845022); + sun.target.position.set(2.9990998365488326, 9.488242347246995, 13.966607385475479); + sun.castShadow = true; + sun.shadow.mapSize.set(2048, 2048); + sun.shadow.camera.left = -30; + sun.shadow.camera.right = 30; + sun.shadow.camera.top = 30; + sun.shadow.camera.bottom = -30; + scene.add(sun); + scene.add(sun.target); + + // ── Static structure (walls/floor/ceiling/fixtures, baked from primitives) ── + const sg = await loadGLTF('./structure.glb'); + sg.scene.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + scene.add(sg.scene); + physics.staticCollider(sg.scene, 'trimesh'); + + // ── Interactive objects (GLB-per-state, with actions/state machines) ─────── + const manifest = await fetch('/scenes/apartment/objects/manifest.json').then((r) => r.json()); + + const assets = manifest.map((entry) => ({ + id: entry.id, + title: entry.title, + transform: entry.transform, + currentStateId: entry.currentStateId, + _shapePivotCenter: entry._shapePivotCenter, + pickable: entry.pickable, + bumpable: entry.bumpable, + bumpResponse: entry.bumpResponse, + bumpDamping: entry.bumpDamping, + castShadow: entry.castShadow, + receiveShadow: entry.receiveShadow, + actions: entry.actions || [], + states: (entry.states || []).map((s) => ({ + id: s.id, + name: s.name, + glbUrl: `/scenes/apartment/objects/${s.file}`, + })), + })); + await loadLevel({ version: '2.0', worldKey: 'default', tags: TAGS, - primitives: PRIMITIVES, - assets: ASSETS, - lights: LIGHTS, - groups: GROUPS, - sceneSettings: { sky: SKY }, + assets, }); return { diff --git a/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png b/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png new file mode 100644 index 0000000000..bb43e9ccca --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:029fc2889f15b8db2bda323f955efc8c40299209c4018c2706ee63c24412981d +size 7132320 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png b/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png new file mode 100644 index 0000000000..063dd24ab8 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04c871f0ab1c5a24ff62e6e927cfa36fb4e48cf5a720708aa19ceff936c6de79 +size 277456 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png b/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png new file mode 100644 index 0000000000..1c025a3c83 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b7865c5ee29745f9bc38182b00b220e88cdf68950422dc664893577cd92689d +size 282900 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png b/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png new file mode 100644 index 0000000000..3c74cbd66b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d2c31fb2341e538f3b9c727de3bb412c7a5a634cae1d32f8cb673ebcd06bea8 +size 265156 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png b/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png new file mode 100644 index 0000000000..7351584764 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0de92d4a4960538f2fb94cc3c8e4886292be1c504c70b6209ce2296bc9889e6e +size 277208 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png b/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png new file mode 100644 index 0000000000..f6169ca91e --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:149df7451fd5cd2088c197c3ca5456d8865e594508570f01066a3a190638c3a9 +size 275116 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png b/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png new file mode 100644 index 0000000000..29381b137f --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18312adcf28b40cb1e0919cf965597714defbce18539d094d614a7e3c89860d4 +size 271888 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png b/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png new file mode 100644 index 0000000000..503875b58a --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b1bd30153ae132d97c1e0db32c4ab5cf8201aeab0195107288c5124928c2a64 +size 275896 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png b/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png new file mode 100644 index 0000000000..3f4266d0b4 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd8eb95b9656b266c6a967afde328b4128764176ab8affe81b315166367a44e +size 265588 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png b/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png new file mode 100644 index 0000000000..ae8fc50a07 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2243b7f1057033b22c45d825c943dd9c30226c9badef03f9ff29866db7d688fd +size 277500 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png b/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png new file mode 100644 index 0000000000..55720fe416 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:226283fd842aafd454396beb374c46a474da7707d1863b57d042445e26a599a4 +size 1421144 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png b/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png new file mode 100644 index 0000000000..1ccad1c8c1 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31e13b5ed8a635c67d83e5307d2b0f06adc947be4e0b27fac0da2f8610f22251 +size 273776 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png b/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png new file mode 100644 index 0000000000..0137f92aa9 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39ae632dc93d53d599e800dded83e6b6a0cf5f7e7dcd69572651b28a5a7b7bb9 +size 265304 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png b/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png new file mode 100644 index 0000000000..1a38e5fdf9 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a473b376851a332a70de287dd6ddb84106c16cf675c178ed90657befdf05378 +size 647932 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png b/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png new file mode 100644 index 0000000000..e82de2f2c5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cefc6fce1c0c9f904afd1cd747ed96abc7035c3a7d76118cfd3d64cae1f969b +size 592716 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png b/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png new file mode 100644 index 0000000000..1081953c3b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fe42c4e887453078288439f61d893d73c29f13ce424346ba2683c51d9ff3c1a +size 273360 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png b/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png new file mode 100644 index 0000000000..56a045da34 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:516aef161ed815e22f25d0f6dcebb9c98ca03383e64bbddaa2930bc6775367f9 +size 528036 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png b/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png new file mode 100644 index 0000000000..de932b55ce --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:593837db8223179ba54ad25e90c4f3edc0c25be2332cb2e742fdef95a41a8037 +size 731636 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png b/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png new file mode 100644 index 0000000000..400dd316bc --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a0cf43d6539c26533d489f6854c9f9218a3668b7794ad3413a03fe6e738ce28 +size 271420 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png b/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png new file mode 100644 index 0000000000..101e5e02c3 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c01ca65028f6506e95898b7a1d0521ab4223c70b3694a1d62bc3380884955dd +size 275832 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png b/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png new file mode 100644 index 0000000000..b23d6a7a9f --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a6d187153f2d634159278dcfdce55eeb2d755a719eea694cadd5beac063c577 +size 1296848 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png b/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png new file mode 100644 index 0000000000..9324122ed5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:748f081db9a0babd8502fe9fb8903f488d01b4a9b32710df7e95bb71970ba008 +size 286084 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png b/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png new file mode 100644 index 0000000000..31c188b3bf --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:762ee1c4a1a1db1d3501dad221f71cdeaf08d90dfda8b1649091946e5bfdeb54 +size 281168 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png b/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png new file mode 100644 index 0000000000..12d9fb52ab --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77a18d10c12dcfb686c14f8cd33b30ba90628d3b3c394218b94b7524c0d8a4d2 +size 269220 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png b/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png new file mode 100644 index 0000000000..1a2e7bbb5b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a60c10294c2d91c582847c11f381435501298b110dc91e4f855ab4f713733d4 +size 277332 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png b/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png new file mode 100644 index 0000000000..5e7992f158 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86f7b7c1dee6ac44532a98cea9b0d8640d0aa0fa7ac64b9d35f9ec7ca3c65301 +size 274048 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png b/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png new file mode 100644 index 0000000000..d3ac676be3 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9286d87f35fbff9d64f1c79f27800642358ecabaf3def562bc804dd5bf93cf01 +size 183576 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png b/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png new file mode 100644 index 0000000000..0712f3cb5b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:936d912a9d9799201c1dba430146118c43c9675ac44f304bf7765d23c57a721a +size 264184 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png b/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png new file mode 100644 index 0000000000..b663744548 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93d78e540cf2241a2f8e17e46cbee336fc2798a6f6900aa041c14ca32727c5e2 +size 264376 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png b/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png new file mode 100644 index 0000000000..c2381f4b6e --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99621b614efba8ba9be34da7448b527d523d38ba7dd3ef36014d66f8aa70d6e4 +size 276804 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png b/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png new file mode 100644 index 0000000000..8f9dc3b917 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cbdb6a74476b4c795fe67c8c3d7521985e602b1c60a753c3eee3351a4364ad7 +size 277920 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png b/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png new file mode 100644 index 0000000000..02b7df1aff --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9edae70daab1b822f48e13c20df4740d31078d056aad185abdc14b63efd74d87 +size 270672 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png b/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png new file mode 100644 index 0000000000..351c7c4887 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a35e1f2bca4505ecfda98a61a39f2486c650b27735a66725f28e0bd466358417 +size 267628 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png b/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png new file mode 100644 index 0000000000..737be207bf --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a84b378c04f0f97f35f8012f6426543e8326dd6441c298f6c021cbd4669a99a5 +size 270912 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png b/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png new file mode 100644 index 0000000000..3bca1ba68f --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b53cdda7af4ed34799bd2efaa1c5d179919124285f37d3ad2beb71b63b5e49c8 +size 519160 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png b/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png new file mode 100644 index 0000000000..1098bfa998 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8800311c40741013d4a9058798e46b77a52fea92d5c61435a991f79567f50fc +size 28358184 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png b/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png new file mode 100644 index 0000000000..0b70f9766c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c297f1edeca7326c7408ae1f8147f9aac236a8f50b6fd3e7914d1c12ea45fea4 +size 282340 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png b/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png new file mode 100644 index 0000000000..cf187f20e2 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca8232ed9640b9f679fff1d0b339e4fbd60532f3b3f6bc056429b0dcd102ceaa +size 135752 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png b/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png new file mode 100644 index 0000000000..87b2bb5919 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2d755c8d03e5e919ec686510d7ad265dc9aa9574f5602cd8258b7f41900f387 +size 268996 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png b/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png new file mode 100644 index 0000000000..c39d8de37b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6b5c937576cb0d5462662c5671c366435391fea85945ba4d935e1592b15fcd0 +size 1429032 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png b/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png new file mode 100644 index 0000000000..9f5c457140 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0be276a3c9143e049bc778a0ba21d2cee688dc49498ca29b1aa7135f56fae2c +size 263664 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png b/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png new file mode 100644 index 0000000000..c6a49fd9eb --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4ca1adbdeae661e94a0063f2702ddd06464338c79bc9fbc5badcb11dd3efea1 +size 264648 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png b/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png new file mode 100644 index 0000000000..ca1f62180b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7e2c3b1b49be3f4aa86d8f7f3c596f8497718fbdedc8038a5260fdfbe287ba8 +size 279252 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png b/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png new file mode 100644 index 0000000000..6e0502b624 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaa5df17ce4675b1823915e7327565bb1a6d36695bbfdfa0b2296f8aa1a61ba1 +size 283264 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png b/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png new file mode 100644 index 0000000000..a169b885af --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fda39aeccd530d8ec2915c2eba00959d3f748b712a4b662330aec552e03f82bc +size 281796 diff --git a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb new file mode 100644 index 0000000000..71c087e61d --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4402409cbbba6ad045503b78cd13db570dfb74e44d6bd9cdbcb4dad6d62f8dd +size 66780 diff --git a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb new file mode 100644 index 0000000000..71c087e61d --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4402409cbbba6ad045503b78cd13db570dfb74e44d6bd9cdbcb4dad6d62f8dd +size 66780 diff --git a/misc/DimSim/scenes/apartment/objects/art-deco-gold-finished-bar-cart-with-two-glass-s/state-default.glb b/misc/DimSim/scenes/apartment/objects/art-deco-gold-finished-bar-cart-with-two-glass-s/state-default.glb new file mode 100644 index 0000000000..d28a610071 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/art-deco-gold-finished-bar-cart-with-two-glass-s/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7ac100213ae3170bd8097c0264c43e08a38724d9c5cd46311be544000df1791 +size 147548 diff --git a/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb b/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb new file mode 100644 index 0000000000..5bd6a69afb --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b72faccf46d202bcb8d159c55e3621dd97052fd3562a0ecc83c2ed92e70ceea +size 71752 diff --git a/misc/DimSim/scenes/apartment/objects/automatic-drip-coffee-maker-with-glass-carafe-i/state-default.glb b/misc/DimSim/scenes/apartment/objects/automatic-drip-coffee-maker-with-glass-carafe-i/state-default.glb new file mode 100644 index 0000000000..64d421304d --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/automatic-drip-coffee-maker-with-glass-carafe-i/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f44258a94a8909d82c67dd688ce8e0366f2912b36f7c0f7871bc50bd4dde26f1 +size 223808 diff --git a/misc/DimSim/scenes/apartment/objects/bathroom-vanity-and-sink/state-default.glb b/misc/DimSim/scenes/apartment/objects/bathroom-vanity-and-sink/state-default.glb new file mode 100644 index 0000000000..c16afae496 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/bathroom-vanity-and-sink/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aee4db9810e0e5cdb842a4b227cb6aa06b08ca63193acb6b2f060df2f0847453 +size 187992 diff --git a/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb b/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb new file mode 100644 index 0000000000..3a9c1c1fe0 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a05fbcd9449cadd8fbdf1d93adf65956030f29282e15fdf3b5743951110c3b8 +size 12292 diff --git a/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb b/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb new file mode 100644 index 0000000000..ed9dd1ac64 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6948f24b677fb73e457bc19ded3274c00f678faa9bbfd370902b1d60c8a4f26 +size 19612 diff --git a/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb b/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb new file mode 100644 index 0000000000..2b255c03d9 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9117229ea2c6e232eeb9fdb2d058d220f82ba0a6160f249ece4e090fc2755ef9 +size 31328 diff --git a/misc/DimSim/scenes/apartment/objects/books/state-default.glb b/misc/DimSim/scenes/apartment/objects/books/state-default.glb new file mode 100644 index 0000000000..e03a6890cb --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/books/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ce9649c4c0567cebb1288a953c8c0428087c49bc75bea6af9cd6f7e28848bd +size 284300 diff --git a/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb b/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb new file mode 100644 index 0000000000..9bd0735229 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bc5bf9736afa07fcca43169bd2d8c6092ae2296471773138ced660dc0ac7c08 +size 65364 diff --git a/misc/DimSim/scenes/apartment/objects/built-in-stainless-steel-dishwasher/state-default.glb b/misc/DimSim/scenes/apartment/objects/built-in-stainless-steel-dishwasher/state-default.glb new file mode 100644 index 0000000000..b5ede05930 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/built-in-stainless-steel-dishwasher/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82447bc193e1f8c3f6e7378e0241d9ac15b155ddf19a1371666c270a9341d49b +size 22280 diff --git a/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb b/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb new file mode 100644 index 0000000000..024a32037e --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:768df13243a11ff2201e37f9b5a99302662dcd3c90a671af64b9fb24eaf257f2 +size 194848 diff --git a/misc/DimSim/scenes/apartment/objects/classic-outdoor-garden-bench/state-default.glb b/misc/DimSim/scenes/apartment/objects/classic-outdoor-garden-bench/state-default.glb new file mode 100644 index 0000000000..0950262c0f --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/classic-outdoor-garden-bench/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514dbc4065f956284818b30a9c280fe8f2ed19eea675d140ad058b71895ca428 +size 56032 diff --git a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb new file mode 100644 index 0000000000..373f3d8cb2 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46db8f9c917f49673f3934bfd70a9ec6d88a8f114098113e2357fc5f829b8dfb +size 165468 diff --git a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb new file mode 100644 index 0000000000..7cb0096127 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:823a130962bde1257cb5aeb69e9375ff407ca6c3548fd20578a2bbd957cc37fd +size 168048 diff --git a/misc/DimSim/scenes/apartment/objects/contemporary-white-ceramic-toilet-with-sleek-lin/state-default.glb b/misc/DimSim/scenes/apartment/objects/contemporary-white-ceramic-toilet-with-sleek-lin/state-default.glb new file mode 100644 index 0000000000..37add0d8fb --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/contemporary-white-ceramic-toilet-with-sleek-lin/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a63bd446c2343599d7576cf40718966fb67135ba5227b98e7bfb57b2967fd3b0 +size 387040 diff --git a/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb b/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb new file mode 100644 index 0000000000..1d4499ec8e --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa831f58667d6a31a4b7e276e83354f4649731f2e7f8bf2646d1f28ddb373e3e +size 54756 diff --git a/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb b/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb new file mode 100644 index 0000000000..9fcb23b239 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98fa7ff02e8c6086ca703fd6b499c4a01c04fbe32516f71c7ea0a72659b80b67 +size 519420 diff --git a/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb b/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb new file mode 100644 index 0000000000..093cfc57ba --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76197c6a01fc4867613b3053040f19929edd300262b77d7f101313a4b03da120 +size 5324 diff --git a/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb b/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb new file mode 100644 index 0000000000..fa618f5251 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2ac86c1ace17f446cbea370201657f5e01c38909850785765026336baa3dbe5 +size 97952 diff --git a/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-default.glb b/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-default.glb new file mode 100644 index 0000000000..753d1637ac --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87b3275bf150a91549829458a45fb566c4012a012916d5e16e6b71628940b273 +size 87080 diff --git a/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-mlsiig1y-6e45.glb b/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-mlsiig1y-6e45.glb new file mode 100644 index 0000000000..daf7654b62 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/freestanding-oval-soaking-bathtub-with-chrome-fl/state-mlsiig1y-6e45.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00164bacaaeb97a03fb1350783fc9bd94d84d3e346ba3d97bd9782bcd5e52e32 +size 87132 diff --git a/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb b/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb new file mode 100644 index 0000000000..1649af14be --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd66639909e00c5ad6dc1895f83ba5e397a0f5ff2bf1d8ec3b80d91334dd64ad +size 69856 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb new file mode 100644 index 0000000000..a341f0fa38 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49561d71d4fbe2a9f24c4d42686c566ce044b672450bf1f5de4e0571f4dcfe85 +size 15184 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb new file mode 100644 index 0000000000..f94c9574e5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc2d86d6406df1a814c6921094ad0dda206de1c5374791dfad3a778f6f6c802f +size 474816 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb new file mode 100644 index 0000000000..b880df573d --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d80266ed16e634b364db9a2154a8aece682d7ff66df2caa6eff1df1895048e +size 291272 diff --git a/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb b/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb new file mode 100644 index 0000000000..d79179ed9a --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da28fb4e304f4989090713118899f3fb593159b57704f19e514f6dc308c4a67a +size 158272 diff --git a/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-default.glb b/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-default.glb new file mode 100644 index 0000000000..6ce0437657 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19ee47ea1a85533b4a93e56d2a7002ce1520bc6f9847aa13c95beb89bc5e72e5 +size 29416 diff --git a/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-mlsr95pw-kpf4.glb b/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-mlsr95pw-kpf4.glb new file mode 100644 index 0000000000..4e558c405c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/kitchen-base-cabinet/state-mlsr95pw-kpf4.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7c1bcd453cd17747720552688f390ffd6fb3000c0eee9ec1e1cd6e18c0aaf19 +size 36348 diff --git a/misc/DimSim/scenes/apartment/objects/kitchen-wall-cabinet/state-default.glb b/misc/DimSim/scenes/apartment/objects/kitchen-wall-cabinet/state-default.glb new file mode 100644 index 0000000000..1d1e246fdc --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/kitchen-wall-cabinet/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0a21a9216f29dd005250b020ba188f82e886b3862957d5f73a5859c18ca2c9c +size 28620 diff --git a/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb b/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb new file mode 100644 index 0000000000..f199a94bb3 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5653f104f577443d6a7e5cf42598f636011c17dcbdc73bde86cff0a1b7d8e43 +size 289124 diff --git a/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb new file mode 100644 index 0000000000..a5a46a5dd6 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ac283bb3f3ea945ae02f59c8046a524bf5513f1f245ca338d3c29e79bfec6f3 +size 130080 diff --git a/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb new file mode 100644 index 0000000000..1d368d2416 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:211e118a515bcc34dfdcc4976335e0885cb11c798a50fdb37c34c7b74db0181c +size 249528 diff --git a/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb new file mode 100644 index 0000000000..1b10a0d35c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:141749af291af410c057d56bd8286a8439d69dc91bdd889fe71ac6ab95735287 +size 51604 diff --git a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb new file mode 100644 index 0000000000..e32b7e317b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7ab1d459f6fb683f6adec3660eee11af5ef19aefb2cfc9ebd06638be952ab0d +size 40948 diff --git a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb new file mode 100644 index 0000000000..d5c6d78782 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd4ca6c219c5f4ee924f09ca6beeb4df857d98507e5abfd9b5d73128f1c4bacf +size 56920 diff --git a/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb b/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb new file mode 100644 index 0000000000..c4635f37d8 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7173622522e2e71b78ff95a86a25daedec44d4ccf5c4a979de848e5a0b8ff3bc +size 20488 diff --git a/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb b/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb new file mode 100644 index 0000000000..8c35ca7096 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a64851cb4fcdd73606d53aa21c14c429e95014739b1fbcbbc975e6d7c3076dd +size 110392 diff --git a/misc/DimSim/scenes/apartment/objects/manifest.json b/misc/DimSim/scenes/apartment/objects/manifest.json new file mode 100644 index 0000000000..039a80bdde --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/manifest.json @@ -0,0 +1,3855 @@ +[ + { + "id": "59a525468c75d8-19c73105016", + "title": "Modern L-shaped sectional", + "transform": { + "position": { + "x": 4.3815894523994166, + "y": 0.49017637093479277, + "z": 1.0556627709770972 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.1238777028003708, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.004147224596688148, + "y": 0.3749999998137355, + "z": 0.44999999254941947 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "modern-l-shaped-sectional/state-default.glb" + } + ] + }, + { + "id": "95e67f414e06f8-19c73110d54", + "title": "Wooden coffee table with glass top", + "transform": { + "position": { + "x": 3.953765809257721, + "y": 0.315281337994577, + "z": 1.7067945710626398 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.0024741389971831285, + "y": 0.23709522248274384, + "z": 0.001997171270521708 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 1.25, + "bumpDamping": 0.78, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wooden-coffee-table-with-glass-top/state-default.glb" + } + ] + }, + { + "id": "2f1b51866fa368-19c731443ef", + "title": "Console with cabinets", + "transform": { + "position": { + "x": 4.516290373580539, + "y": 0.3523583672864196, + "z": 4.64489994581474 + }, + "rotation": { + "x": 0, + "y": 3.141592653589793, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.24142332983411488, + "z": 0.027500016428739227 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsncwfv-pq43", + "label": "to Open", + "from": "state-default", + "to": "state-mlsncwfv-pq43" + }, + { + "id": "cycle-state-mlsncwfv-pq43-to-state-default", + "label": "to closed", + "from": "state-mlsncwfv-pq43", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "console-with-cabinets/state-default.glb" + }, + { + "id": "state-mlsncwfv-pq43", + "name": "Open", + "file": "console-with-cabinets/state-mlsncwfv-pq43.glb" + } + ] + }, + { + "id": "c7899f757219b-19c731bc6ff", + "title": "Television", + "transform": { + "position": { + "x": 4.500068482229571, + "y": 1.6470746382091632, + "z": 4.880000008134797 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-mlr4bgf0-u7gv", + "_shapePivotCenter": { + "x": 3.862724577460491, + "y": 0.5565298706483899, + "z": 1.7678768512278153 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-mlr4bgf0-u7gv-to-state-mlr4dvjl-fyw3", + "label": "to ON", + "from": "state-mlr4bgf0-u7gv", + "to": "state-mlr4dvjl-fyw3" + }, + { + "id": "cycle-state-mlr4dvjl-fyw3-to-state-mlr4bgf0-u7gv", + "label": "to OFF", + "from": "state-mlr4dvjl-fyw3", + "to": "state-mlr4bgf0-u7gv" + } + ], + "states": [ + { + "id": "state-mlr4bgf0-u7gv", + "name": "OFF", + "file": "television/state-mlr4bgf0-u7gv.glb" + }, + { + "id": "state-mlr4dvjl-fyw3", + "name": "ON", + "file": "television/state-mlr4dvjl-fyw3.glb" + } + ] + }, + { + "id": "227cb298980678-19c731c8141", + "title": "Modern tripod floor lamp", + "transform": { + "position": { + "x": 2.567363667122909, + "y": 0.9266403965704086, + "z": 0.7462406515396548 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.021859171998984822, + "y": 0.8218468760394194, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlrpcv6c-bdvt", + "label": "to OFF", + "from": "state-default", + "to": "state-mlrpcv6c-bdvt" + }, + { + "id": "cycle-state-mlrpcv6c-bdvt-to-state-default", + "label": "to ON", + "from": "state-mlrpcv6c-bdvt", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "ON", + "file": "modern-tripod-floor-lamp/state-default.glb" + }, + { + "id": "state-mlrpcv6c-bdvt", + "name": "OFF", + "file": "modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb" + } + ] + }, + { + "id": "3b515be13388f-19c73217ec3", + "title": "Wooden bookcase", + "transform": { + "position": { + "x": -1.2346250672621193, + "y": 1.0120243146713455, + "z": 0.21597170828388723 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.2804687702858426, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.8999999999999999, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wooden-bookcase/state-default.glb" + } + ] + }, + { + "id": "0d3779109193e-19c73221b10", + "title": "bookshelf decor", + "transform": { + "position": { + "x": -1.2161971652872015, + "y": 0.7367178620425292, + "z": 0.21783707347967085 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.08705190594401346, + "y": 0.10999999791383742, + "z": -0.0016066725690125835 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "bookshelf-decor/state-default.glb" + } + ] + }, + { + "id": "d76362662545e-19c73234c4d", + "title": "Books", + "transform": { + "position": { + "x": -1.4493133008996957, + "y": 1.4775923795984425, + "z": 0.2510956471722847 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0012008581219888764, + "y": 0.04850000034272671, + "z": -0.0021056519411290844 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "books/state-default.glb" + } + ] + }, + { + "id": "578967ee4707-19c73238f45", + "title": "Hardcover books 1", + "transform": { + "position": { + "x": -0.8614216537918351, + "y": 1.14, + "z": 0.26 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 1.5707963267948966 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.006083928007335378, + "y": 0.09999999932944774, + "z": 0.0030000000000000027 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "hardcover-books-1/state-default.glb" + } + ] + }, + { + "id": "1a72e35e19a66-19c73244ffc", + "title": "Small potted plant", + "transform": { + "position": { + "x": -1.4087380833018583, + "y": 1.184871881798844, + "z": 0.25297856262312113 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.14893394079338146, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "small-potted-plant/state-default.glb" + } + ] + }, + { + "id": "22db76c93c7648-19c7324b0a5", + "title": "Hardcover books", + "transform": { + "position": { + "x": -1.552216185656096, + "y": 0.29590875727765287, + "z": 0.24483101995851997 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.004628634243017425, + "y": 0.048, + "z": 0.003862298942239717 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "hardcover-books/state-default.glb" + } + ] + }, + { + "id": "28f8380d2b909-19c7325a1fe", + "title": "Queen size bed", + "transform": { + "position": { + "x": -4.893117299804146, + "y": 0.68, + "z": -2.45 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.6000000000000001, + "z": -0.07500001341104512 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "queen-size-bed/state-default.glb" + } + ] + }, + { + "id": "c73ce00cad78a8-19c732af8bf", + "title": "Bedside table", + "transform": { + "position": { + "x": -5.64, + "y": 0.39, + "z": -1.2651768042674925 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.5019873160601556, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.275, + "z": 0.01249999839812517 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsm2hfh-kpff", + "label": "to Open", + "from": "state-default", + "to": "state-mlsm2hfh-kpff" + }, + { + "id": "cycle-state-mlsm2hfh-kpff-to-state-default", + "label": "to Closed", + "from": "state-mlsm2hfh-kpff", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "Closed", + "file": "bedside-table/state-default.glb" + }, + { + "id": "state-mlsm2hfh-kpff", + "name": "Open", + "file": "bedside-table/state-mlsm2hfh-kpff.glb" + } + ] + }, + { + "id": "392a951bb6f11-19c732bcfa3", + "title": "Bedside table", + "transform": { + "position": { + "x": -5.628831317188218, + "y": 0.372181049562469, + "z": -3.622031341394607 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.5, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.275, + "z": 0.01249999839812517 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsm2hfh-kpff", + "label": "to Open", + "from": "state-default", + "to": "state-mlsm2hfh-kpff" + }, + { + "id": "cycle-state-mlsm2hfh-kpff-to-state-default", + "label": "to Closed", + "from": "state-mlsm2hfh-kpff", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "Closed", + "file": "bedside-table/state-default.glb" + }, + { + "id": "state-mlsm2hfh-kpff", + "name": "Open", + "file": "bedside-table/state-mlsm2hfh-kpff.glb" + } + ] + }, + { + "id": "d1b8634521f5b-19c732c7954", + "title": "Hardcover books", + "transform": { + "position": { + "x": -5.536708061318499, + "y": 0.45461773108196735, + "z": -3.702951078012202 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.004628634243017425, + "y": 0.048, + "z": 0.003862298942239717 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "hardcover-books/state-default.glb" + } + ] + }, + { + "id": "cffc759645ff18-19c732db260", + "title": "Books", + "transform": { + "position": { + "x": -5.580755687496764, + "y": 0.4376603227117231, + "z": -1.2697435441517564 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.0849412180967812, + "y": 0.77, + "z": 0.77 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0012008581219888764, + "y": 0.04850000034272671, + "z": -0.0021056519411290844 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "books/state-default.glb" + } + ] + }, + { + "id": "bc6db6f4cbd0a-19c732ef8c8", + "title": "Modern Office Work Desk", + "transform": { + "position": { + "x": -1.7301176406514844, + "y": 0.4654592368201924, + "z": -0.43338758405308886 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.37499999267980455, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "modern-office-work-desk/state-default.glb" + } + ] + }, + { + "id": "5028f5326c4f1-19c73300d7f", + "title": "Work Chair", + "transform": { + "position": { + "x": -1.6718826945870606, + "y": 0.6166519299880042, + "z": -1.539229575343378 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 0.6439314083963319, + "y": 0.6439314083963319, + "z": 0.6439314083963319 + } + }, + "currentStateId": "state-mlrqdudw-h1ax", + "_shapePivotCenter": { + "x": -0.21270116532945352, + "y": -0.3700314207695614, + "z": -0.9905300125758202 + }, + "pickable": false, + "bumpable": true, + "bumpResponse": 0.65, + "bumpDamping": 0.77, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-mlrqdudw-h1ax", + "name": "default", + "file": "work-chair/state-mlrqdudw-h1ax.glb" + } + ] + }, + { + "id": "ac7a816a60265-19c73317f64", + "title": "Arc floor lamp", + "transform": { + "position": { + "x": -2.333111809397252, + "y": 0.8985525485842618, + "z": -0.4373821778992827 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 0.7651227023559565, + "y": 0.7651227023559565, + "z": 0.7651227023559565 + } + }, + "currentStateId": "state-mlskixi4-xebf", + "_shapePivotCenter": { + "x": 0.7149999964237214, + "y": 1.055747293351406, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlskixi4-xebf", + "label": "to OFF", + "from": "state-default", + "to": "state-mlskixi4-xebf" + }, + { + "id": "cycle-state-mlskixi4-xebf-to-state-default", + "label": "to ON", + "from": "state-mlskixi4-xebf", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "ON", + "file": "arc-floor-lamp/state-default.glb" + }, + { + "id": "state-mlskixi4-xebf", + "name": "OFF", + "file": "arc-floor-lamp/state-mlskixi4-xebf.glb" + } + ] + }, + { + "id": "1765e77823bda8-19c7334745f", + "title": "Large wardrobe", + "transform": { + "position": { + "x": -0.31533036247765267, + "y": 1.1536078665542793, + "z": -4.546448663974232 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.3917312529711456, + "y": 1, + "z": 1.091767035307015 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 1.0499999996274711, + "z": 0.02000000406056643 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsmklqi-mykf", + "label": "to Open", + "from": "state-default", + "to": "state-mlsmklqi-mykf" + }, + { + "id": "cycle-state-mlsmklqi-mykf-to-state-default", + "label": "to closed", + "from": "state-mlsmklqi-mykf", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "large-wardrobe/state-default.glb" + }, + { + "id": "state-mlsmklqi-mykf", + "name": "Open", + "file": "large-wardrobe/state-mlsmklqi-mykf.glb" + } + ] + }, + { + "id": "6eaff768b565c-19c7335c107", + "title": "Freestanding oval soaking bathtub with chrome fl...", + "transform": { + "position": { + "x": 4.522408021754294, + "y": 0.3904049244978556, + "z": -0.896791648070859 + }, + "rotation": { + "x": 0, + "y": 3.141592653589793, + "z": 0 + }, + "scale": { + "x": 1.7873490005753465, + "y": 1, + "z": 1.3568453847354858 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.08813499016471649, + "y": 0.6175507578992459, + "z": 4.231659860618338 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsiig1y-6e45", + "label": "to Full", + "from": "state-default", + "to": "state-mlsiig1y-6e45" + }, + { + "id": "cycle-state-mlsiig1y-6e45-to-state-default", + "label": "to Empty", + "from": "state-mlsiig1y-6e45", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "Empty", + "file": "freestanding-oval-soaking-bathtub-with-chrome-fl/state-default.glb" + }, + { + "id": "state-mlsiig1y-6e45", + "name": "Full", + "file": "freestanding-oval-soaking-bathtub-with-chrome-fl/state-mlsiig1y-6e45.glb" + } + ] + }, + { + "id": "b500b00d33e638-19c735e91d7", + "title": "refrigerator", + "transform": { + "position": { + "x": -2.6390041499833767, + "y": 0.9876455923302134, + "z": 0.5193998030296436 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.0032454665083127, + "y": 0.9003749143341613, + "z": -0.003915878407667384 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsqs928-jonu", + "label": "to Open", + "from": "state-default", + "to": "state-mlsqs928-jonu" + }, + { + "id": "cycle-state-mlsqs928-jonu-to-state-default", + "label": "to closed", + "from": "state-mlsqs928-jonu", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "refrigerator/state-default.glb" + }, + { + "id": "state-mlsqs928-jonu", + "name": "Open", + "file": "refrigerator/state-mlsqs928-jonu.glb" + } + ] + }, + { + "id": "669fe9f9f97588-19c736a9260", + "title": "kitchen base cabinet", + "transform": { + "position": { + "x": -3.6717847955793625, + "y": 0.529275586728742, + "z": 0.4720761398832385 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.8098083678676107, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.0004609774656834098, + "y": 0.4399999994598329, + "z": 0.01919808945853102 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsr95pw-kpf4", + "label": "to open", + "from": "state-default", + "to": "state-mlsr95pw-kpf4" + }, + { + "id": "cycle-state-mlsr95pw-kpf4-to-state-default", + "label": "to closed", + "from": "state-mlsr95pw-kpf4", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "kitchen-base-cabinet/state-default.glb" + }, + { + "id": "state-mlsr95pw-kpf4", + "name": "open", + "file": "kitchen-base-cabinet/state-mlsr95pw-kpf4.glb" + } + ] + }, + { + "id": "a4013b97f887b8-19c736d15ce", + "title": "kitchen base cabinet", + "transform": { + "position": { + "x": -5.578769058377267, + "y": 0.5351143306110472, + "z": 4.341854069829404 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.7788682026204685, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.0004609774656834098, + "y": 0.4399999994598329, + "z": 0.01919808945853102 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsr95pw-kpf4", + "label": "to open", + "from": "state-default", + "to": "state-mlsr95pw-kpf4" + }, + { + "id": "cycle-state-mlsr95pw-kpf4-to-state-default", + "label": "to closed", + "from": "state-mlsr95pw-kpf4", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "kitchen-base-cabinet/state-default.glb" + }, + { + "id": "state-mlsr95pw-kpf4", + "name": "open", + "file": "kitchen-base-cabinet/state-mlsr95pw-kpf4.glb" + } + ] + }, + { + "id": "414821fb84733-19c736ec63b", + "title": "kitchen base cabinet", + "transform": { + "position": { + "x": -5.581997975509333, + "y": 0.5282067101293415, + "z": 1.7664241653590549 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.4227205704111852, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.0004609774656834098, + "y": 0.4399999994598329, + "z": 0.01919808945853102 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlsr95pw-kpf4", + "label": "to open", + "from": "state-default", + "to": "state-mlsr95pw-kpf4" + }, + { + "id": "cycle-state-mlsr95pw-kpf4-to-state-default", + "label": "to closed", + "from": "state-mlsr95pw-kpf4", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "closed", + "file": "kitchen-base-cabinet/state-default.glb" + }, + { + "id": "state-mlsr95pw-kpf4", + "name": "open", + "file": "kitchen-base-cabinet/state-mlsr95pw-kpf4.glb" + } + ] + }, + { + "id": "80182e12bc169-19c73a10d27", + "title": "Professional Gas Range", + "transform": { + "position": { + "x": -4.746920983597513, + "y": 0.5687589957482349, + "z": 0.3873462875499325 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.1219595276194203, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.4899999935925007, + "z": 0.030999994091689576 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlssjm1p-xotl", + "label": "turn on front right", + "from": "state-default", + "to": "state-mlssjm1p-xotl" + }, + { + "id": "it-mlsszajl-asw9", + "label": "turn on back right", + "from": "state-default", + "to": "state-mlsskac7-ywao" + }, + { + "id": "it-mlsszdl0-9396", + "label": "turn on front left", + "from": "state-default", + "to": "state-mlssl1do-1yyl" + }, + { + "id": "cycle-state-mlssjm1p-xotl-to-state-mlsskac7-ywao", + "label": "turn off", + "from": "state-mlssjm1p-xotl", + "to": "state-default" + }, + { + "id": "cycle-state-mlsskac7-ywao-to-state-mlssl1do-1yyl", + "label": "turn off", + "from": "state-mlsskac7-ywao", + "to": "state-default" + }, + { + "id": "cycle-state-mlssl1do-1yyl-to-state-default", + "label": "turn off", + "from": "state-mlssl1do-1yyl", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "off", + "file": "professional-gas-range/state-default.glb" + }, + { + "id": "state-mlssjm1p-xotl", + "name": "front-right-on", + "file": "professional-gas-range/state-mlssjm1p-xotl.glb" + }, + { + "id": "state-mlsskac7-ywao", + "name": "back-right-on", + "file": "professional-gas-range/state-mlsskac7-ywao.glb" + }, + { + "id": "state-mlssl1do-1yyl", + "name": "front-left-on", + "file": "professional-gas-range/state-mlssl1do-1yyl.glb" + } + ] + }, + { + "id": "5aca293afb4bc8-19c73a60e13", + "title": "Built-in stainless steel dishwasher", + "transform": { + "position": { + "x": -5.62, + "y": 0.52, + "z": 3.5 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.42500000000000004, + "z": 0.016000004224479197 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "built-in-stainless-steel-dishwasher/state-default.glb" + } + ] + }, + { + "id": "62797796e9767-19c73af66a5", + "title": "Sink", + "transform": { + "position": { + "x": -5.59988395161818, + "y": 0.9535132879737271, + "z": 2.6793443991438437 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 0.7160886522166464, + "y": 0.40892918087790386, + "z": 0.8280100914128449 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.2733975025605969, + "y": 1.2202618787565602, + "z": 1.8803600531622169 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "sink/state-default.glb" + } + ] + }, + { + "id": "80c4a613fb7268-19c73c9ac7a", + "title": "Dining chair", + "transform": { + "position": { + "x": -1.0087908140402728, + "y": 0.5434133631727004, + "z": 4.26 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.42500000000000004, + "z": 0.00026035857325493184 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "dining-chair/state-default.glb" + } + ] + }, + { + "id": "9a67b23a28cf8-19c73cb7fe7", + "title": "Large ornate mahogany dining table", + "transform": { + "position": { + "x": -0.3670123249376638, + "y": 0.47636876395355976, + "z": 3.809280713021919 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.37499999936670064, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "large-ornate-mahogany-dining-table/state-default.glb" + } + ] + }, + { + "id": "b521e3d18de31-19c73cc4ab7", + "title": "Dining chair", + "transform": { + "position": { + "x": -1.021304087860674, + "y": 0.5331213412094118, + "z": 3.31 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.42455268602766005, + "z": 0.00026035857325493184 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "dining-chair/state-default.glb" + } + ] + }, + { + "id": "c4896d1ee8f58-19c73cca1c3", + "title": "Dining chair", + "transform": { + "position": { + "x": 0.0258311295758461, + "y": 0.5434518887030261, + "z": 4.257087223482772 + }, + "rotation": { + "x": 0, + "y": 4.71238898038469, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.42455268602766005, + "z": 0.00026035857325493184 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "dining-chair/state-default.glb" + } + ] + }, + { + "id": "5870c87d73e6e-19c73cd1041", + "title": "Dining chair", + "transform": { + "position": { + "x": 0.4759727625914219, + "y": 0.5487097275235999, + "z": 3.6356960320731617 + }, + "rotation": { + "x": 0, + "y": 4.71238898038469, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.42455268602766005, + "z": 0.00026035857325493184 + }, + "pickable": false, + "bumpable": true, + "bumpResponse": 1.25, + "bumpDamping": 0.86, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "dining-chair/state-default.glb" + } + ] + }, + { + "id": "86897b7437ec4-19c73d08c40", + "title": "kitchen wall cabinet", + "transform": { + "position": { + "x": -5.7339840704817675, + "y": 2.1862344570288634, + "z": 4.379152567928243 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.2867158952939255, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.44999999999999996, + "z": 0.01999999690800905 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "kitchen-wall-cabinet/state-default.glb" + } + ] + }, + { + "id": "d4e7cf45e14168-19c73d2c59e", + "title": "kitchen wall cabinet", + "transform": { + "position": { + "x": -5.734054470140882, + "y": 2.185881752908841, + "z": 3.4550831669585023 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.44999999999999996, + "z": 0.01999999690800905 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "kitchen-wall-cabinet/state-default.glb" + } + ] + }, + { + "id": "53ffa35fcc3b3-19c73d3c12b", + "title": "Wall-mounted stainless steel chimney range hood\n...", + "transform": { + "position": { + "x": -4.740618632346517, + "y": 2.4462029494015645, + "z": 0.3129874596416138 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1.3048132595239712, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.49499999426305297, + "z": 0.004999999888241291 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wall-mounted-stainless-steel-chimney-range-hood/state-default.glb" + } + ] + }, + { + "id": "16b55c06139a58-19c73d4f998", + "title": "kitchen wall cabinet", + "transform": { + "position": { + "x": -5.7404835482395695, + "y": 2.183006970153812, + "z": 1.6857432491479991 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.5347140343971224, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.44999999999999996, + "z": 0.01999999690800905 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "kitchen-wall-cabinet/state-default.glb" + } + ] + }, + { + "id": "2841a95215e2b-19c73d6a74f", + "title": "Two-slice chrome toaster with browning control d...", + "transform": { + "position": { + "x": -5.53024050161023, + "y": 1.0784581482665967, + "z": 1.5159435448934113 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.02000000216066837, + "y": 0.0950000018440187, + "z": 0.006250037542275558 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "two-slice-chrome-toaster-with-browning-control-d/state-default.glb" + } + ] + }, + { + "id": "d146e81adcfb6-19c73d6f1d1", + "title": "Automatic drip coffee maker with glass carafe\n\nI...", + "transform": { + "position": { + "x": -5.528265138870446, + "y": 1.1498788004517702, + "z": 4.666337898166567 + }, + "rotation": { + "x": -3.141592653589793, + "y": 1.2353040676263245, + "z": -3.141592653589793 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.17499999983236195, + "z": 0.017000000141561028 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "automatic-drip-coffee-maker-with-glass-carafe-i/state-default.glb" + } + ] + }, + { + "id": "b9d431e7a8db-19c73d7935a", + "title": "Modern cordless electric kettle with brushed met...", + "transform": { + "position": { + "x": -4.737252095959948, + "y": 1.1082720912950728, + "z": 0.3542968078051474 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.125, + "z": -0.004422579425060177 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "modern-cordless-electric-kettle-with-brushed-met/state-default.glb" + } + ] + }, + { + "id": "d27de72b5e0e88-19c73d801ac", + "title": "High-speed kitchen blender with glass jar and me...", + "transform": { + "position": { + "x": -5.598440540578837, + "y": 1.1821618341549747, + "z": 0.39175803651442287 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.03320012858728233, + "y": 0.21249999860301613, + "z": 0.003750044043080797 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb" + } + ] + }, + { + "id": "e9ad475608341-19c73d95aaf", + "title": "Art deco gold-finished bar cart with two glass s...", + "transform": { + "position": { + "x": 1.1617934539333223, + "y": 0.5389120073767927, + "z": 0.3863584703065891 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.43250041351490937, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "art-deco-gold-finished-bar-cart-with-two-glass-s/state-default.glb" + } + ] + }, + { + "id": "2366af35eadcf8-19c73da0dcc", + "title": "Detailed porcelain dinner plate with gold rim an...", + "transform": { + "position": { + "x": 1.2032666946891117, + "y": 0.35538681201078215, + "z": 0.457809393418966 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.015000253395232687, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb" + } + ] + }, + { + "id": "9614072b45308-19c73da284e", + "title": "Detailed porcelain dinner plate with gold rim an...", + "transform": { + "position": { + "x": 1.1965681617029897, + "y": 0.42327413092342614, + "z": 0.5170610864361298 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.015000253395232687, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb" + } + ] + }, + { + "id": "b56a8224e0b41-19c73da6a7c", + "title": "Detailed porcelain dinner plate with gold rim an...", + "transform": { + "position": { + "x": 1.2651117557232083, + "y": 0.8539772864259029, + "z": 0.40488177152786675 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.015000253395232687, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb" + } + ] + }, + { + "id": "35b2a660f6f678-19c73db81a7", + "title": "Detailed porcelain dinner plate with gold rim an...", + "transform": { + "position": { + "x": 1.264493009384819, + "y": 0.8980005677819676, + "z": 0.4371962543984047 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.015000253395232687, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb" + } + ] + }, + { + "id": "995aeff211eac-19c73dc9250", + "title": "Wine glass", + "transform": { + "position": { + "x": 1.0177538809809166, + "y": 0.9429782021892605, + "z": 0.4498776100590399 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-mlrogc5m-n4t3", + "_shapePivotCenter": { + "x": 0, + "y": 0.11125000042840838, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [ + { + "id": "cycle-state-default-to-state-mlrogc5m-n4t3", + "label": "to full", + "from": "state-default", + "to": "state-mlrogc5m-n4t3" + }, + { + "id": "cycle-state-mlrogc5m-n4t3-to-state-default", + "label": "to empty", + "from": "state-mlrogc5m-n4t3", + "to": "state-default" + } + ], + "states": [ + { + "id": "state-default", + "name": "empty", + "file": "wine-glass/state-default.glb" + }, + { + "id": "state-mlrogc5m-n4t3", + "name": "full", + "file": "wine-glass/state-mlrogc5m-n4t3.glb" + } + ] + }, + { + "id": "0695d8eaecdef-19c73dd202d", + "title": "Salt and pepper Shakers", + "transform": { + "position": { + "x": -3.9803931457097406, + "y": 1.0048145678774343, + "z": 0.5215084811167934 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.03500000027939677, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "salt-and-pepper-shakers/state-default.glb" + } + ] + }, + { + "id": "3501961e59bfd-19c73dd7348", + "title": "Woven rattan placemat", + "transform": { + "position": { + "x": -0.6918154969521217, + "y": 0.8540616060927329, + "z": 3.2486867503780283 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.006163739011521584, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "woven-rattan-placemat/state-default.glb" + } + ] + }, + { + "id": "106836e6061308-19c73ddca05", + "title": "Woven rattan placemat", + "transform": { + "position": { + "x": -0.03199168640492356, + "y": 0.8639676887097529, + "z": 3.2080873974631094 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.006163739011521584, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "woven-rattan-placemat/state-default.glb" + } + ] + }, + { + "id": "a4d9f0c23dd5b-19c73ddea29", + "title": "Woven rattan placemat", + "transform": { + "position": { + "x": -0.6857401667092435, + "y": 0.859627743601119, + "z": 4.289689745315966 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.006163739011521584, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "woven-rattan-placemat/state-default.glb" + } + ] + }, + { + "id": "86bf2f5237ec6-19c73de0fe1", + "title": "Woven rattan placemat", + "transform": { + "position": { + "x": -0.04934858727521346, + "y": 0.8540464700703785, + "z": 4.288064914106616 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.006163739011521584, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "woven-rattan-placemat/state-default.glb" + } + ] + }, + { + "id": "bd9f45e625fdc-19c73de49ea", + "title": "Stainless steel cutlery set", + "transform": { + "position": { + "x": -0.696829349219807, + "y": 0.8586490872300028, + "z": 3.32 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0025628975396361324, + "y": 0.0027103629905995495, + "z": 0.014999998435378074 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "stainless-steel-cutlery-set/state-default.glb" + } + ] + }, + { + "id": "0a096032464cd8-19c73df0150", + "title": "Stainless steel cutlery set", + "transform": { + "position": { + "x": -3.9286632656874647, + "y": 0.5386453681978114, + "z": 0.6449646063146555 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0025628975396361324, + "y": 0.0027103629905995495, + "z": 0.014999998435378074 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "stainless-steel-cutlery-set/state-default.glb" + } + ] + }, + { + "id": "0202ee297f2a88-19c73df2eb8", + "title": "Stainless steel cutlery set", + "transform": { + "position": { + "x": -3.7662372755085376, + "y": 0.5388803818702548, + "z": 0.6515124115358037 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0025628975396361324, + "y": 0.0027103629905995495, + "z": 0.014999998435378074 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "stainless-steel-cutlery-set/state-default.glb" + } + ] + }, + { + "id": "94ecfd5ada2578-19c73dfac8d", + "title": "Stainless steel cutlery set", + "transform": { + "position": { + "x": -3.4404169853090836, + "y": 0.5413681306400779, + "z": 0.6380915974649233 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0025628975396361324, + "y": 0.0027103629905995495, + "z": 0.014999998435378074 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "stainless-steel-cutlery-set/state-default.glb" + } + ] + }, + { + "id": "e504c978d237c-19c73e648dc", + "title": "Sleek countertop microwave oven with digital dis...", + "transform": { + "position": { + "x": -5.59, + "y": 1.1109554476439747, + "z": 4.160795697533484 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.16000000309199097, + "z": 0.019999998398125177 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "sleek-countertop-microwave-oven-with-digital-dis/state-default.glb" + } + ] + }, + { + "id": "7c166d4a7b1fa-19c73e6b7ae", + "title": "Classic outdoor garden bench", + "transform": { + "position": { + "x": 3.6298746749997752, + "y": 0.46974768857104965, + "z": 7.2158786326308135 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.4101636643918064, + "z": -0.009999999776482582 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "classic-outdoor-garden-bench/state-default.glb" + } + ] + }, + { + "id": "30e5d8717fd2e8-19c7406b70c", + "title": "Bathroom vanity and sink", + "transform": { + "position": { + "x": 2.031272123883404, + "y": 0.6635385096694033, + "z": -4.330352147122318 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 1.456095909396544, + "y": 0.4909199642562849, + "z": 0.26005917572635695 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "bathroom-vanity-and-sink/state-default.glb" + } + ] + }, + { + "id": "3e3ea9b2cc2e58-19c74073206", + "title": "Contemporary white ceramic toilet with sleek lin...", + "transform": { + "position": { + "x": 4.454104862841407, + "y": 0.42316131113123934, + "z": -4.505567046787334 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.3957356681124615, + "y": 1.3957356681124615, + "z": 1.3957356681124615 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.37999999845400456, + "z": 0.0024999931454658397 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "contemporary-white-ceramic-toilet-with-sleek-lin/state-default.glb" + } + ] + }, + { + "id": "94e9a636240718-19c7407bbee", + "title": "Luxury rainfall shower head and wall-mounted mix...", + "transform": { + "position": { + "x": 1.9425969244634211, + "y": 1.3148567749089612, + "z": -0.36640908300765984 + }, + "rotation": { + "x": 0, + "y": 3.141592653589793, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 1.25, + "z": 0.26500000309199095 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb" + } + ] + }, + { + "id": "a2b85a007ca9d-19c7408763c", + "title": "Woven wicker laundry hamper with a fabric liner ...", + "transform": { + "position": { + "x": 3.25028196420934, + "y": 0.3302800075837711, + "z": -4.503918870307929 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 0.8583300892907466, + "y": 0.8583300892907466, + "z": 0.8583300892907466 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.29749999690800905, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb" + } + ] + }, + { + "id": "99c77c594d079-19c7408ea1d", + "title": "Polished chrome wall-mounted towel rack with fol...", + "transform": { + "position": { + "x": 1.2000208284940208, + "y": 1.53, + "z": -1.1057607374920708 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.4494794852373833, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.26249999122687057, + "z": 0.01807261087466902 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb" + } + ] + }, + { + "id": "7e1f8523afa01-19c74098514", + "title": "Minimalist floating wooden wall shelves for bath...", + "transform": { + "position": { + "x": 4.491771043106426, + "y": 1.2101214572034933, + "z": -0.15247527825167362 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.1833166424840937, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 1.4399999997206032, + "z": 0.07500000000000001 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb" + } + ] + }, + { + "id": "ba520c65d11258-19c7409f996", + "title": "Matching ceramic soap dispenser, toothbrush hold...", + "transform": { + "position": { + "x": 1.3341187797982013, + "y": 1.1268225562070888, + "z": -4.449625881687194 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.019999999552965164, + "y": 0.08499999845400452, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb" + } + ] + }, + { + "id": "9ea7f64c32fb7-19c740b89fc", + "title": "Wall-mounted medicine cabinet with a large mirro...", + "transform": { + "position": { + "x": 2.017737545060936, + "y": 1.9269932324049326, + "z": -4.810104392332999 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.303586220666433, + "y": 1.1705937734630785, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.03941424555273487, + "y": 0.4, + "z": 0.022500016540531666 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wall-mounted-medicine-cabinet-with-a-large-mirro/state-default.glb" + } + ] + }, + { + "id": "b45b699007c72-19c74300203", + "title": "Framed abstract oil painting", + "transform": { + "position": { + "x": 4.424584573788344, + "y": 1.9470570099444073, + "z": 0.08375157009131762 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1.3643420085814393, + "y": 1.2248492171872085, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 1.5, + "z": 0.0017383729780349189 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "framed-abstract-oil-painting/state-default.glb" + } + ] + }, + { + "id": "0d071b9d15a778-19c74323057", + "title": "Framed landscape oil painting", + "transform": { + "position": { + "x": -3.608992492739797, + "y": 1.7361845431468765, + "z": -4.879999937970252 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 2.321890853006333, + "y": 2.518815242069379, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.30000000000000004, + "z": 0.002609475582382474 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "framed-landscape-oil-painting/state-default.glb" + } + ] + }, + { + "id": "8f61d882176858-19c743358f5", + "title": "Vintage poster", + "transform": { + "position": { + "x": -4.4079772102754955, + "y": 1.8962601991548873, + "z": -0.06366045843035233 + }, + "rotation": { + "x": 0, + "y": 3.141592653589793, + "z": 0 + }, + "scale": { + "x": 2.0446682506880354, + "y": 2.1001934576595302, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.32499999999999996, + "z": 0.004999999944120646 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "vintage-poster/state-default.glb" + } + ] + }, + { + "id": "46aa48c4bb14d-19c74347b30", + "title": "wall clock", + "transform": { + "position": { + "x": 5.862637492866764, + "y": 2.36, + "z": 2.88 + }, + "rotation": { + "x": 0, + "y": 4.71238898038469, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.4, + "z": 0.005000036787878975 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wall-clock/state-default.glb" + } + ] + }, + { + "id": "ba2c0f11cedef-19c743507ed", + "title": "Wall mirror", + "transform": { + "position": { + "x": -1.8721821641931122, + "y": 1.8546174723983981, + "z": 3.9556118892043464 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.75, + "z": 0.027505963917087026 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "wall-mirror/state-default.glb" + } + ] + }, + { + "id": "39e52be49c9ec8-19c74363fc3", + "title": "Assorted decorative plants", + "transform": { + "position": { + "x": -2.9044516167844154, + "y": 0.6125478909283358, + "z": 5.464666979023578 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": -0.06828595502296761, + "y": 0.4995002142584293, + "z": 0.05005857882475222 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "assorted-decorative-plants/state-default.glb" + } + ] + }, + { + "id": "412ff4ad131ff-19c74372469", + "title": "Set of three canvas prints featuring detailed bo...", + "transform": { + "position": { + "x": -5.890814574323871, + "y": 2.1254741150774024, + "z": -2.4937709011301235 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.7741064961903168, + "y": 1.3607416183002867, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.30000000000000004, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb" + } + ] + }, + { + "id": "048418404b31-19c7437cce5", + "title": "Neon light wall art", + "transform": { + "position": { + "x": 0.9, + "y": 1.65, + "z": -0.94 + }, + "rotation": { + "x": 0, + "y": 4.71238898038469, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.0455026046119541, + "y": 0.31117674360714764, + "z": -0.009999999944120645 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "neon-light-wall-art/state-default.glb" + } + ] + }, + { + "id": "eedf6074a66ce-19c7438a204", + "title": "Bohemian style woven macrame", + "transform": { + "position": { + "x": 5.856553202676937, + "y": 1.79, + "z": -2.44 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.5152472768993592, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "bohemian-style-woven-macrame/state-default.glb" + } + ] + }, + { + "id": "998aaee1750c98-19c7441f967", + "title": "Chunky knit wool throw blanket with visible weav...", + "transform": { + "position": { + "x": -4.722957697499421, + "y": 0.5995631183491226, + "z": -2.443810500735502 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1.319078993031496, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.04748437812509175, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb" + } + ] + }, + { + "id": "94d820b5be849-19c7442b3c5", + "title": "Pair of plush white bed pillows with cotton text...", + "transform": { + "position": { + "x": -5.649719211474874, + "y": 0.7187351739850696, + "z": -2.4659646636791352 + }, + "rotation": { + "x": 1.5707963267948963, + "y": 1.050976584154359, + "z": -1.5707963267948963 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.11000000000000001, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb" + } + ] + }, + { + "id": "2668d614a5e848-19c7443bb04", + "title": "Soft fuzzy stuffed teddy bear with button eyes a...", + "transform": { + "position": { + "x": -0.8510426342848817, + "y": 1.0184118900507562, + "z": -4.745109333958126 + }, + "rotation": { + "x": 0, + "y": -0.35751964509038986, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.1524781208620488, + "z": 0.022493639105132107 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb" + } + ] + }, + { + "id": "72878a8f5ffb28-19c74452ecd", + "title": "laptop open", + "transform": { + "position": { + "x": -1.7, + "y": 0.96, + "z": -0.41 + }, + "rotation": { + "x": 0, + "y": 3.141592653589793, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.11673651631330051, + "z": -0.04432948718632643 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "laptop-open/state-default.glb" + } + ] + }, + { + "id": "be0c1bf4a1e43-19c74455b1e", + "title": "Matte ceramic coffee mug with steam and realisti...", + "transform": { + "position": { + "x": -2.084297221134241, + "y": 0.9317528639491932, + "z": -0.46062232996302266 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.01750006346140766, + "y": 0.09077258996382548, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "matte-ceramic-coffee-mug-with-steam-and-realisti/state-default.glb" + } + ] + }, + { + "id": "e891daddeb566-19c7445a6c2", + "title": "Hardcover book lying open with paper pages and p...", + "transform": { + "position": { + "x": 5.107398196850658, + "y": 0.5789292014696342, + "z": 1.379850337825023 + }, + "rotation": { + "x": 0, + "y": -0.7614917198077298, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.010374427221847459, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb" + } + ] + }, + { + "id": "466b5c32d8ffc8-19c7445fe6d", + "title": "Thin glass and metal smartphone with reflective ...", + "transform": { + "position": { + "x": -5.620367368438998, + "y": 0.6507814526590434, + "z": -3.5722425565895612 + }, + "rotation": { + "x": 0, + "y": 0.5836908321898583, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.00625, + "z": -7.82310966007671e-10 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "thin-glass-and-metal-smartphone-with-reflective/state-default.glb" + } + ] + }, + { + "id": "bfeb41524c65f8-19c74468aea", + "title": "Light oak wooden bed tray with handles and short...", + "transform": { + "position": { + "x": -4.810490635848853, + "y": 0.696442809417525, + "z": -2.773194360540799 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.12499999903142453, + "z": 0 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb" + } + ] + }, + { + "id": "bf9beb5fb79a8-19c744766ad", + "title": "Thick quilted duvet neatly folded with soft fabr...", + "transform": { + "position": { + "x": -1.0909513930543349, + "y": 0.2944852328458841, + "z": -4.680966959464118 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 0.43288237437984695, + "y": 1, + "z": 0.6923437151644042 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 0.11199358191488266, + "z": 0.055000002682209004 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb" + } + ] + }, + { + "id": "dbefaeda89b1d-19c744ed429", + "title": "Large detailed oak tree with textured bark and l...", + "transform": { + "position": { + "x": -1.5512307374727492, + "y": 2.5134853929878243, + "z": 8.016079845065434 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 0.6996114045873896, + "y": 0.6996114045873896, + "z": 0.6996114045873896 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0, + "y": 3.649999976158142, + "z": 0.25000003576278695 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb" + } + ] + }, + { + "id": "60e08e06242f38-19c74564e1e", + "title": "Galvanized steel watering can with a long spout ...", + "transform": { + "position": { + "x": 5.241405996940184, + "y": 0.21187023344930833, + "z": 5.203786043018361 + }, + "rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "scale": { + "x": 1, + "y": 1, + "z": 1 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.09620328438469122, + "y": 0.16122611763747985, + "z": 0 + }, + "pickable": true, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "galvanized-steel-watering-can-with-a-long-spout/state-default.glb" + } + ] + }, + { + "id": "843c6307ab323-19c7456cda8", + "title": "Large terracotta garden pots with intricate flor...", + "transform": { + "position": { + "x": 5.795020447146578, + "y": 0.22466053141207248, + "z": 8.532342244184631 + }, + "rotation": { + "x": 0, + "y": 1.5707963267948966, + "z": 0 + }, + "scale": { + "x": 0.5624222520343933, + "y": 0.5624222520343933, + "z": 0.5624222520343933 + } + }, + "currentStateId": "state-default", + "_shapePivotCenter": { + "x": 0.32500000298023224, + "y": 0.31999999359250064, + "z": -2.980232227667301e-9 + }, + "pickable": false, + "bumpable": false, + "bumpResponse": 0.9, + "bumpDamping": 0.9, + "castShadow": false, + "receiveShadow": false, + "actions": [], + "states": [ + { + "id": "state-default", + "name": "default", + "file": "large-terracotta-garden-pots-with-intricate-flor/state-default.glb" + } + ] + } +] \ No newline at end of file diff --git a/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb b/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb new file mode 100644 index 0000000000..7544e36ec5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cbd612bf6d352856f370f4e6fec5593829a5e06ed4a2097e856a1df5703cdb1 +size 123028 diff --git a/misc/DimSim/scenes/apartment/objects/matte-ceramic-coffee-mug-with-steam-and-realisti/state-default.glb b/misc/DimSim/scenes/apartment/objects/matte-ceramic-coffee-mug-with-steam-and-realisti/state-default.glb new file mode 100644 index 0000000000..4a12d24d2c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/matte-ceramic-coffee-mug-with-steam-and-realisti/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f8198ef23fc9c4e2b8eee2ca7dfef585eba254db817ccc124b51e14a97d7cb9 +size 67604 diff --git a/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb b/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb new file mode 100644 index 0000000000..372baf0ee2 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:447433e32bfc2f6dad6c998df0ff352577e4e35759455b079324c47c17bfc5be +size 66600 diff --git a/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb new file mode 100644 index 0000000000..b9b0b71815 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3b8641521299e073c3fc8034ae10b23111c96aff801849417f6f2b481c6528f +size 137580 diff --git a/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb new file mode 100644 index 0000000000..d11cb0d228 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7707ff7cc37565fa00812a279b94f263885b1023bc71eb816dbf13faf9348dbb +size 717952 diff --git a/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb new file mode 100644 index 0000000000..f1adea26e5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c7f2a70785473f91365b98caa526d53a960b320792904f08fb27603338ce06f +size 128712 diff --git a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb new file mode 100644 index 0000000000..d8dd2492d4 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3608c64008626bc1eb78503363aa0688f5baedca9fbdba4aff663602247d467c +size 68484 diff --git a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb new file mode 100644 index 0000000000..b73c480c32 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:778ab2cbb680be0b67bce8062f760198879846fa5ff4125f49e115c15abd207b +size 68528 diff --git a/misc/DimSim/scenes/apartment/objects/neon-light-wall-art/state-default.glb b/misc/DimSim/scenes/apartment/objects/neon-light-wall-art/state-default.glb new file mode 100644 index 0000000000..c09176be7b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/neon-light-wall-art/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7e5c1efd9d82139f895d0f45138d04b112750b9769e609aa1380c573905dd12 +size 30864 diff --git a/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb b/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb new file mode 100644 index 0000000000..f4a8e74740 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4ad78901c92d872372b15693d8160d0ba6d339ce8c1d2d4d00ffde515868617 +size 190364 diff --git a/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb b/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb new file mode 100644 index 0000000000..b0fab3a30c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c10671b111d6d1b450291dd002c00ce0458e82fe3a757290492fdd1fab0ba2 +size 240916 diff --git a/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-default.glb b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-default.glb new file mode 100644 index 0000000000..c15cb087a0 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8109e458f1d0a27e9d393ae4977a64b6feba9cac916a610310232ec1e18b2c9 +size 121688 diff --git a/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssjm1p-xotl.glb b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssjm1p-xotl.glb new file mode 100644 index 0000000000..67522886d2 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssjm1p-xotl.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfebe1e8414475946e88dc2beeb162259b0a546582a9788fb897e7161ef13094 +size 130176 diff --git a/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlsskac7-ywao.glb b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlsskac7-ywao.glb new file mode 100644 index 0000000000..77e45f5b9e --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlsskac7-ywao.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03040168b854d3a5daaa057f3d0ff616a0258b133365594ab77dcfdfb82d2cd3 +size 121688 diff --git a/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssl1do-1yyl.glb b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssl1do-1yyl.glb new file mode 100644 index 0000000000..4de8b27e9a --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/professional-gas-range/state-mlssl1do-1yyl.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15cfb6492ed1b20d6d3db50c83c17cca3a8cacf91eaf10220b9bd890100f589c +size 121736 diff --git a/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb b/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb new file mode 100644 index 0000000000..c7ac6e7742 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b05a0a072c24fa603dd41fa762016b467069123126352c7f5c249698f23c3e9 +size 474460 diff --git a/misc/DimSim/scenes/apartment/objects/refrigerator/state-default.glb b/misc/DimSim/scenes/apartment/objects/refrigerator/state-default.glb new file mode 100644 index 0000000000..295ca7d29a --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/refrigerator/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e48c5600a1aa5cf3ac9878135051cf9f14ae4cb73d48281964c2a30c40f8fa5d +size 49068 diff --git a/misc/DimSim/scenes/apartment/objects/refrigerator/state-mlsqs928-jonu.glb b/misc/DimSim/scenes/apartment/objects/refrigerator/state-mlsqs928-jonu.glb new file mode 100644 index 0000000000..1762ee4b5f --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/refrigerator/state-mlsqs928-jonu.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a7aba2a1d9b35334833240e84c88960bbb81e4f99dd9b114c2ca7dacce82101 +size 70212 diff --git a/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb b/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb new file mode 100644 index 0000000000..a2e739efa8 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6006144d35d1273e67ff0536be6d184c41053cb4303262fe0a773f432a3aee2 +size 52228 diff --git a/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb b/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb new file mode 100644 index 0000000000..a3a5b282b0 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b22d1a75f3ea4d66c53bf8d984a1cb5ff507d1cdde687263653655acc44cbad +size 7764 diff --git a/misc/DimSim/scenes/apartment/objects/sink/state-default.glb b/misc/DimSim/scenes/apartment/objects/sink/state-default.glb new file mode 100644 index 0000000000..544388128b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/sink/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d8f9552fc6c3f80aa7552b0e04d44541dedef1ce2ef60ccc5579bafd8fdb156 +size 83804 diff --git a/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb b/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb new file mode 100644 index 0000000000..f9adf371d1 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaeaa8161e3af4411aaededfe8fe4772a50ca5db5ff07135a444466f81449e10 +size 143480 diff --git a/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb b/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb new file mode 100644 index 0000000000..d69b029176 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8177152a0cbe9cdf89fb2906e146fcbb8ed7fff683bb78cd55a7898bad7fe3d +size 27244 diff --git a/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb b/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb new file mode 100644 index 0000000000..04856ebb05 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fc2e3d0fa90fa8402b96100c1010bc32a96d46ec22b7ab0024a9e111215512f +size 248264 diff --git a/misc/DimSim/scenes/apartment/objects/stainless-steel-cutlery-set/state-default.glb b/misc/DimSim/scenes/apartment/objects/stainless-steel-cutlery-set/state-default.glb new file mode 100644 index 0000000000..04f263073c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/stainless-steel-cutlery-set/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bd120f6e539d9b8abafb49944da66eedf09bb48acefc9d0d3101030fcbd5407 +size 877620 diff --git a/misc/DimSim/scenes/apartment/objects/television/state-mlr4bgf0-u7gv.glb b/misc/DimSim/scenes/apartment/objects/television/state-mlr4bgf0-u7gv.glb new file mode 100644 index 0000000000..e9279f3b0b --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/television/state-mlr4bgf0-u7gv.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:613f4a3ad1cf067e613b8117e4ce1388ab1e5f7ecb829cd29195010a07479fb1 +size 12224 diff --git a/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb b/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb new file mode 100644 index 0000000000..768cc61231 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcedd8eb975635630ed9abdce28de8ddecc27237dfea24ecfa12b7ff92f872e9 +size 12516 diff --git a/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb b/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb new file mode 100644 index 0000000000..625b5fb404 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:195eea6a93ae9f816c6d7cd7bc26ca7564225c8dfe11c49c8258d45b0d3d35ee +size 198920 diff --git a/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb b/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb new file mode 100644 index 0000000000..2687710f29 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df968d04ffb906955a0274dd0bcf9969eb73f0bdd4ead3f7180567d2daaf28bf +size 198372 diff --git a/misc/DimSim/scenes/apartment/objects/two-slice-chrome-toaster-with-browning-control-d/state-default.glb b/misc/DimSim/scenes/apartment/objects/two-slice-chrome-toaster-with-browning-control-d/state-default.glb new file mode 100644 index 0000000000..ade7ec72c9 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/two-slice-chrome-toaster-with-browning-control-d/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a56148c04372525971900d49f8d182193d2656427fda6da240f450e9e9ca9fed +size 141696 diff --git a/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb b/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb new file mode 100644 index 0000000000..143e198581 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:500b9701f0655995f71c552bae214177226ab8eb46bf85ae8d7b54f962523fcb +size 15524 diff --git a/misc/DimSim/scenes/apartment/objects/wall-clock/state-default.glb b/misc/DimSim/scenes/apartment/objects/wall-clock/state-default.glb new file mode 100644 index 0000000000..09e0c05efc --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wall-clock/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5477e7fae01230e75880560cf89c197859739403b7740f9d4eb5c810beee49ff +size 59196 diff --git a/misc/DimSim/scenes/apartment/objects/wall-mirror/state-default.glb b/misc/DimSim/scenes/apartment/objects/wall-mirror/state-default.glb new file mode 100644 index 0000000000..37763e3567 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wall-mirror/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:903c82b9c38f1855eda06867f4002e0db9aacfc715a0d27ab8f22baf2b8ee4ba +size 154116 diff --git a/misc/DimSim/scenes/apartment/objects/wall-mounted-medicine-cabinet-with-a-large-mirro/state-default.glb b/misc/DimSim/scenes/apartment/objects/wall-mounted-medicine-cabinet-with-a-large-mirro/state-default.glb new file mode 100644 index 0000000000..883bea2886 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wall-mounted-medicine-cabinet-with-a-large-mirro/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca773d8aa40e2d11f105401140ec7c8a7a62e7efde70abeb85ff394d31f40734 +size 18044 diff --git a/misc/DimSim/scenes/apartment/objects/wall-mounted-stainless-steel-chimney-range-hood/state-default.glb b/misc/DimSim/scenes/apartment/objects/wall-mounted-stainless-steel-chimney-range-hood/state-default.glb new file mode 100644 index 0000000000..e144c4e813 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wall-mounted-stainless-steel-chimney-range-hood/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49f50c5fa351fc3eb37f75ccbc0def6503b87c6d9de193307d98afc8e8f8494c +size 155312 diff --git a/misc/DimSim/scenes/apartment/objects/wine-glass/state-default.glb b/misc/DimSim/scenes/apartment/objects/wine-glass/state-default.glb new file mode 100644 index 0000000000..2cd5e484e5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wine-glass/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fe7aa25d676188b282aebaf26bb83c80847681229023b8c3f86387f64bb5332 +size 26092 diff --git a/misc/DimSim/scenes/apartment/objects/wine-glass/state-mlrogc5m-n4t3.glb b/misc/DimSim/scenes/apartment/objects/wine-glass/state-mlrogc5m-n4t3.glb new file mode 100644 index 0000000000..b8209dd163 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wine-glass/state-mlrogc5m-n4t3.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:762815672b79d4db9364d5d0c65e47f2062606131cf50a8e944231f99bc6c40f +size 34824 diff --git a/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb b/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb new file mode 100644 index 0000000000..6a58f0b629 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17cea02c7e72813f3d3d6545c3e155c16b71ac68dc28c0a74df9c9f00b0c8dc0 +size 16868 diff --git a/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb b/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb new file mode 100644 index 0000000000..dff460e58c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ebcfd3c2c8fe6ab67ad76d83f52752a6836a49a75a4834c2664586d8ffc378e +size 948808 diff --git a/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb b/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb new file mode 100644 index 0000000000..47f6f6e0f5 --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:046b039e29f764963ffcaacacec81f5c54faa7d5055e1e1a95967ab68419b71d +size 869368 diff --git a/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb b/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb new file mode 100644 index 0000000000..d29145807c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51c5782524ebd9ee9638e5e9f64f99d1cafc3947e2c3dafe25ff8eed391b3f9e +size 21376 diff --git a/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb b/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb new file mode 100644 index 0000000000..e259eb446c --- /dev/null +++ b/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1ed7a07c90c81045c0259828cd0e2a2cd19e7ebf5d750b38947a989ade5387e +size 28580 diff --git a/misc/DimSim/scenes/apartment/structure.glb b/misc/DimSim/scenes/apartment/structure.glb new file mode 100644 index 0000000000..8394981f5b --- /dev/null +++ b/misc/DimSim/scenes/apartment/structure.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d1d4ae848c8d6378e1f6a03aba55dfeeb47a6b42010be065ed93c76a6ba3736 +size 5800704 diff --git a/misc/DimSim/scenes/apartment/textures/048224345952.webp b/misc/DimSim/scenes/apartment/textures/048224345952.webp deleted file mode 100644 index 66aead2a3f19bb42aa673b1a44a8eaf04cd4abb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136276 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ⅇ9XxuoSd9m_)f4bkYz14V0r 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�o@yCKNxN4Nm*JH}D7k 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 V07l;b6n7senEzs(f1Z7a{{XVif_?x1 diff --git a/misc/DimSim/scenes/apartment/textures/08e5e2b877e1.jpg b/misc/DimSim/scenes/apartment/textures/08e5e2b877e1.jpg deleted file mode 100644 index 8eb3b637dd..0000000000 --- a/misc/DimSim/scenes/apartment/textures/08e5e2b877e1.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08e5e2b877e108e4da37c6bf25dd32ff4dc66063e19867aca454c69b3b1a1f44 -size 44165 diff --git a/misc/DimSim/scenes/apartment/textures/1c8ef077e400.jpg b/misc/DimSim/scenes/apartment/textures/1c8ef077e400.jpg deleted file mode 100644 index b2408f888e..0000000000 --- a/misc/DimSim/scenes/apartment/textures/1c8ef077e400.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c8ef077e400ce9948c50d1f9dbf294798e29bcf2ffcd5f00ded7bd52a83a51c -size 103818 diff --git a/misc/DimSim/scenes/apartment/textures/2518091b2c71.jpg b/misc/DimSim/scenes/apartment/textures/2518091b2c71.jpg deleted file mode 100644 index ba9b7b0770..0000000000 --- a/misc/DimSim/scenes/apartment/textures/2518091b2c71.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2518091b2c71ad2d4f2890ffb3716a0589db4f4a3beb2862fc9e9b65af08e1fb -size 60094 diff --git a/misc/DimSim/scenes/apartment/textures/2b666e22f0a6.jpg b/misc/DimSim/scenes/apartment/textures/2b666e22f0a6.jpg deleted file mode 100644 index c86d88dc44..0000000000 --- a/misc/DimSim/scenes/apartment/textures/2b666e22f0a6.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b666e22f0a66ba45edfd1a9681aa8a05690ac9479620d90babdac2d692c8a72 -size 435881 diff --git a/misc/DimSim/scenes/apartment/textures/3885a68cb0cb.webp b/misc/DimSim/scenes/apartment/textures/3885a68cb0cb.webp deleted file mode 100644 index ed925ea86e74f8c8450379c1a8c682d3b4f4e057..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13220 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 WNEOCSIZDapr?$-|{q+c5qyPZ!uez83 diff --git a/misc/DimSim/scenes/apartment/textures/4d5b048c925d.avif b/misc/DimSim/scenes/apartment/textures/4d5b048c925d.avif deleted file mode 100644 index e266581ac75179ed71ba1a711675676cb607f993..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65914 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 diff --git a/misc/DimSim/scenes/apartment/textures/84fdd8bbff5e.jpg b/misc/DimSim/scenes/apartment/textures/84fdd8bbff5e.jpg deleted file mode 100644 index 210cbc9e37..0000000000 --- a/misc/DimSim/scenes/apartment/textures/84fdd8bbff5e.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84fdd8bbff5ec6bac51689c3148009b86eb984404d46267baa02a6fbd69f21d7 -size 89399 diff --git a/misc/DimSim/scenes/apartment/textures/9049fe942dc4.jpg b/misc/DimSim/scenes/apartment/textures/9049fe942dc4.jpg deleted file mode 100644 index 8e92fb45f0..0000000000 --- a/misc/DimSim/scenes/apartment/textures/9049fe942dc4.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9049fe942dc4a45cb88f13ca4e97c0be43a1163686e216125132eb9b1ddf238b -size 558867 diff --git a/misc/DimSim/scenes/apartment/textures/a2ba694ad53e.jpg b/misc/DimSim/scenes/apartment/textures/a2ba694ad53e.jpg deleted file mode 100644 index 840d6264b1..0000000000 --- a/misc/DimSim/scenes/apartment/textures/a2ba694ad53e.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2ba694ad53e044e64a2ad9a59089c41f8b04fcd3edc72c630984c151c31d779 -size 25789 diff --git a/misc/DimSim/scenes/apartment/textures/af07500a616c.webp b/misc/DimSim/scenes/apartment/textures/af07500a616c.webp deleted file mode 100644 index ee5c45e3fd1c7ad66558111a33bdb0d2116c4bf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14942 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 c$jqp}qF%jaU;)&<6X(4V}eaip<0G_rCa{vGU diff --git a/misc/DimSim/scenes/apartment/textures/c83879b7f455.jpg b/misc/DimSim/scenes/apartment/textures/c83879b7f455.jpg deleted file mode 100644 index 28c8812d59..0000000000 --- a/misc/DimSim/scenes/apartment/textures/c83879b7f455.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c83879b7f4559548b15bcd391cf552ab49e809ffa23703c3d6b990c4df7df758 -size 86539 diff --git a/misc/DimSim/scenes/apartment/textures/d4679a510dcb.avif b/misc/DimSim/scenes/apartment/textures/d4679a510dcb.avif deleted file mode 100644 index e63c32a06cbe9227820602f98a9ef0a75fe5143a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1631263 zcmbTd1#lcekZ3t0W@ct)MvIwcF*7qWGc&WrvRD>d%*-rVXfZRNyc>Ua?;`%ci>ryK zdX<$mnVnS~-Pt_@004;0Ts$2N-7L)jU*%tGYiY)8YiVdEC&Vla0Kf~{nz|VN8~ZB6 z7A7`M|A_#AgQc;{|Kk7EA~;yO*#1Wc_*D~F+SnWa>xlvYz^~808USwrz*=hgVk%2Z zyZ-NB9b(9w=s2bp;t6Cwl;J!Ari5-buhH^1OWc?nE%ZJp!|z1(N~Z;n7KJP z=vf%P!vA08|2X(xTK{MGx3>SOajWznYX-!a_;1;N&;4(ieGve_b^jHcg#VTqWdZ;# zp#T8Z+JDQ)3IG7a5CEWg`hOe`#=rey;o{=J!^r6F?#^IoYRvF2p#N3=UlsmK^M4Qj z!ydywd;b+XB2iN_LsuIYqJKgC+VS?TPDIWQhQ_8u^#9LI{QrC6|8VPn_(7*^YG&$W zYWHPI?Q54=+L?bXx1EWli>19Ck)_@LZiWAUPWvA|{KNm;*I$5I`~g7IX8<5gqX9tA zCjelmumDhM>wnWs*8_@Dd!i@(PIqyE2DAn{*a5NAtsqJLr$WfdZ0 zS0}fB{B=(JtH1#;07L*902@F6AO%nX=m9JMP5>`J5FiGS1}Fej0Ga?jfHA-VU<+^p zxC49ufq*bTG#~+x3djQF1Bw9^fEqvppaswg=miV`#sM>cMZh{>8*l(P1zZ6h0IxtG z5E2LvLq5`4|Vh-X6;sp`{5(kn2QUvl7 zq#2|aWE^A>WEN(@R5$^|L{ssO45Y6j{A>IWJHng&_~S_9e+It)4w zx(#{;`V0mEh6+XmMi0gdCJCkrW(?*C<_8u7mIYQ0)(kcPHV?J~b_MnU4iAn8P7BTh zE(NXuZVv7a9tNHUUJCvTd=PvQ`~ds`0s;a9f)augLJ~p~!V1D0A_gKCq86eDVisZ# z;vNzb5*v~hk{?n5(h$-GG7K^k@+V|BG!is9G!L`_v<;WR910u_oCur_oGV-$Tp3&s+$!8HJUl!Fydb<5ybF9B zdx$`6!&ls!~XR8mwCRAba& z)MC^@)B`jKG)gonG%K`dv}&{|v@3KJbT)J~bPx1w^xx>)7@!yw7*ZHE81Wbl7)uy0 znE04Nn5LMKm^GMln2%VvSb|ulSW#HDSc_OM*hJW3*w)yI*e%$bIAAz5I7&F~IC(gO zIA^$MxV*T=xKX$bxNCSIcrFAol2DSUk_3^|l5CQ~kaCfl zk*1LLkzSDzlF59&v$nEcv(d7dvgNU@u%oiev4^wwvA=V0ayW6+a2#`zaT;*ua4vJ9aVc`eaE)+- za|?6(b9Zt-^KkIE@HFsT^3wBK^H%a6@lo=b@)h&#@sscy@)z=Ne=ND*J(A#(GLngs%ToAK#!?khSJIr)zS09Sa5CRzvSfB;X=I&b+vPyy zq~((3Hss0W?c`e&018qH$qJi_l!}gu9ZC>Nib~l^f0db)eUyj4qkh-_Uitk&MOYt0(-J5~EYhg~O3 zXGxb**F$$y4@b{N@3%gpzM+1j0l0y>L8ZZ)p@LzN;k}WRQI64-v50ZH@u`WRNs7s_ zDZgow>7g0FS(4e2Ilpj>t<@gmC7~Lb;s?STejPi zyPA8W2eOBq$Al-1XQbz$m$+AnH<-7vcb^ZjPoU3^uaIw{AJEUpug{;fC%qeU+oHsl_0xZHZVk(j~GBffc$|Pzm znkhOh`YpydW-OLDHY4^k&Ma;!o+Cal0V2UJVJT4{u{;Sm$t!6uSuVLbg(xK|6Yv*nM)xpqF)=Au%{Tuyvd>2etNY`h#SNDC7L(f^SdGB7Ie&2e(M*qTq z(!kW9%;3n7_)!0_&~Wz%|48R3&uH5i*I3Iq$9VGu`^2wF_Q_vU98=BHoYSo{+%xU7 ze6wA10&~6dqVq!wQVZjY@{6-es!J=&I?LNDCM!p)HmlcbZfmdW0UO{OQJYAcXm=g5zA5MvH0=KiT26Csl(~( zS=c%9dCmpZMbo9=<;0c7)xovX_2*5@E%t5M9ot>sz2g1WgYCoXW7HG&Q~5L3^YDxM z%i*j08{}KYJN0|}hs?*ur|swG*YzLuUru3XY~%5D_W%!o2>23VSvuGnezpI~SF!^R z1PKNM|4MbbZA}baPyjGcc#I5NEPPOcEM?Wk$W#ELASf!p+&=vCPwLs!L=do4;OrqH zbG{A-T8Q(Fm4&=r8V@*sF`pVnnn95YM>TU;&|SL~9m#OF7m&}|eb|M7-;|;wi(Ozg zj>fCLsO)uZjk}l`Ct(M{Gd@gU@Mq)?Sl{W^XLw$TwwU*>#DQej&4*Ee24~LUC-l^} zE3=~0j;XGEsEtZ4@rt#4gsfX%6{{aoK`b8>=)5MEK_%J=B6L&RHmz9scn=E~8akTq zpy18Wa~cr{vsgUe^0x?7%1Z!^2{jzPPsZC@qi6Bmx`G708>zUUIQD_A6HVFD9491r z&xEP2YNoeiOyPf??`Ugu?+I$wH%2r`^kEM|*g+|w(j2=bIY1r+i9+xZ*Hqv8)+v9e z@tu<|*(U>ZARk_TJ||lVRoSWAoP--hTR zo){!`g;*#*cy%YY8mXnUz(lXvc#KyEjYr1C zHORc~<;Q*~Z(Ma29eT$5C`^EWDEZ@#x>?}vV=nYcGC`pDU3i|VX9wcx(a(o8-lOd_ z*`jQJQMrCjfuPMK*I2DwwdDd`dn9J9e)YX}H3o+sM^Q0)bYklkgIqMYwW(1S=&}OS zei%ut%e5AmfP`p!LscH(P1Wyux8+rTO(j&Z4AMN3?@lm5&lD*f%g_f~=O(cx2yfC6 zQkv||v4i6PF+Cn?3wjY=vi{*8RWu|uTc|MdR)k6?r~*GA8sj=-MNu)P_Yjj;SY6(w zblOSqJ6Td#QQBeGdYc|p9x1*EC&OC$ZVtPAQ{N?yqVjXv~u#YTnA+-Imb-3T~Vl@lB$b z-_*;XQvMLl?vzZOrW0Z@zNg%NddBf>XSSOqBX~dkD!K9UkUlVqS4=7;j|Kq|?%`&Dj0n@Ce4EC3G+oFHON@GNImZiGq1bR>3tO1UUj3_#Ls- zzv@W*h>R2<0Mlmf%;-i@MFQC?#mKw72!gm9UC23}zPq+6*G`sYGShNtvhJ>Jo$Orq z*@A^$*sF4Yk8ae#1nnDjouhXb+NAfwI$nGXrIt$iNw)gC(>$maYqqO>(PAfiQe1xS zcdP=k3K~${*?D9Eh4q}zl7mm1`ws;{yc0&_FiL3K8hLOumd2@K^4c zIwxx>g6C5T9pf2X+a0Ok%x*f=tb{Q7ldAHyaWNrE&+L){L z!ImU$R+HZRar4AWh&upGPhp49dMCb?&wU%`xeB@Hd_Od}vZ|v+kJX#-qzexg)qtF%WGkL0zYdgZcTsS~qPkpG!4Emi9iZF$gb-{}X)hcTl zx#>>1(Vg3Q>s9D@@P^{xS4msUWC|5kR8vYgvsL}W3176M+6EaNZ9S_${u#2t^Itc$G1>owjf&t}7oyAzd+Eke#_*ufr?J4K2o|1W27yn=<|FpYRGH&QM%RYaxa| z<)Yu-1#Qug^hEb+|260egt#?eNPccl8KlNRrqVrl&Jv8CEe_|F``!2E;R+UYkI=La@hQU2R11ckx-$AVB2I%) zbAlwzllzwT)egQ6E3*2p#a;_HUUN46X4zE|wMYa5TU~qs z(MvHqp*_|U=VI-8K7ZI8k4U4<)T+?pB&T*ga-L(xcP(+QNk@aL3mv83y-~&9!)^Pb z>-KhbXm0o{MxQqdqPOK>$Hd8#s%3&4Hsyq6{o+**sPAY?kM}Rbq{hbRVsYTMqC4}G z?f&g2yGqP6qb-Gy<*12OQN33l^{>Bmy+zqi6GM9JX_|_9(Q=x^1*%nRaEUHV@`l=w zmzFU;+k-=^PpAAHuEEB85u1yJX4wMuWKX>)LsNOyIG}Q(?s+oU6vjvQ^}~;xN_Wu^q6N0@@_&qc*avVa%By=n(pl(rq)9R0Nz> zM7%M%2)86lMJ&s}_Ag(3TqX8m*Zxun8U&L6@I94)60$wbftOB=V!Z9fmRJlW3vZDI2*3TZ zYh|6?s_a^(v(BUK&rW&qv`?^B(f4HbvJScQouV#$HaQ4KC%&KvUsQ$>YRL{CG- zhH{DFUEHAU!*Hi4hl4D#63+W-GX*d3D+$M(4wIRcB%*}D*=Lk-W>eUTC!0n@e+4q? z7OECzb-k!&+4DQpaq|MhK`_NC4Mag$QSdZFW_x3Tyx`w09xCi=g6dEe5CQT-@_tOF zC1Hxr1DZ?5Qj3+5a;Z zWvdJ>Rj;_`!=ZY)g4Dz&n>A+{$Ub5E^<%Zd2o1Zg2Y8r9Wr-)}-3)B%8}r z4OG`J+`lb~cE?l$!|>ho{2(XjXL6pa|7fk}6WgOuq%HWiXMmRz$!642vu#YJ!6G-0 z#GuMB268hk7QPC(FlAotUM31$+oaOmF{fZj#`n_`yOF(EJK!ft2&?Q3Hx+#}BLg;A zc<6MvnAB`OIo*`EaO2oVphGohrO9Eg}K>Wsi*i&hArm@=Zr@VPmk_ENg;wS zi+J|6w}m~Y7ys!VWv?5-X14w&J|!=PW+>2i--U~#r^(~6vn{z$c)uEey1&gbd9j=J)U zQ{+PvDO%xHYT%K4X0^V+M!vBFEx^PX?}*&!YUwH{52CZMmbtj1km#Ua9bzSsLzp!ZV6pt%aX*8I6?vq+bZeVAC)gb=kGY7x-H=~AqV_lCg1x%HKsN2xmOJWUVBN`>!qs= z5CyNxp#<$c^VgBJ43tpaILPm)_C`M^;vI`4?D{-`vw;e^H`un^^ znp`FS>|hD&)+(z0(ZF2NzP*orNiw%dKke#P&Hawk>QXWlGHIe{s{3R}@8SuNh{HRxR+w*qOy|ubjnTEwg z+iS3tPHpEbntJOfi*aHPRh`zPso$n&Atvnr{0~`Jp~4y*lNwG(n4FpoxCwt4!7Oq7 z8@&3iRDlV7$O%gP zf!4>XK*K`(oinfb1}7?_yj=HekIfi?`TiMOJ+;riev3Fu64`tq)mgE;M$ztuOIh^~ zd}}b_4@c(WebJ`*W7NkiDvSB-4~^R2pC$XU2i2iElf8QdPgH-vwH2w5V`mqp3g#wepEnC8(jJ05pDC=v3+}2J87%(XhSC*YxV|=(w|Ga)^>HM+svGI|nL7#p{%qsQ3*`{ocWwj$q9`^zsp*a6| zw*CT*!8+O2U@)`CS=(199Duwoj)R0JcnD#azgPb=3+s*(B)d(5eY zZsk94AcceoXkf}2V{q1`>Dw2w>%{{Jy6-C z>Gd4(&Cuh->$XpL{b@f==D`77W~R8cj@sbRFw@56>NSp5E*X(Ub7akCsLsz zsLG*dP-Y%Xdsy+yEIRTP&MQAg)hpCH?al&yxtOHXzW9p5WF4{*V(lcNjUDQdvi}jH zqyCh`E&6$uRMuno=h?SZO^aFK>tLhr9A;TJ14+c~Rw2GJNko&GSAUp|GmoZoo&%$P z8LTyvv?a0mI1_Bh_=JSMrfBUNYbHLC;51l}rM##+8&1V0#mbO}mqlOXsHwUW5iHvQ zIz`lU1WpB7vLj8@B$z|1Ph=(}T>OlN9HXoReaNRNRyl8-3;>@fS<_p3>&0GjwaUd# zI9$^Tf9ZQ_HrwHNFSv}_$UHd5lW1A@UVn(;x>xGnt(rWx6~sNcp@fgR%6Laz%Qjzs;HTDGHNXY>IjD5R4e;ecq zL5}b~8G`y8XD*g=EZxgt#&Dau;(6At??Cu4!S?nVr1Ga4@-;m=$oZMkfv8OfE;F+# zRD2WK_;xBpxg6IZd-{GdC}F3| zF4v4e&eZ-gJNxq9ZWw}3HX;tR@REtF#cvoP&x7y4--}tfSp9qgcwY50^Qmje`B53b zY(WMTU0-@vM{>W)X&H6zzfvUNX*jPtU+zBc5EU^O!_G6L)yN!zN4*8levd$&SWzLU z&K4bwJ`jL};m5NcF3mt%(A*SiU|w`BFA-&r8Xo3vn|tRdb!ERox&f#zPN2b$!THZj zm_j&VC!d@uOQx2wt z9p(HH7UGQwN-j^#D5V~aN0;Q3_(DzNo(G;MpE8#HU^fG>`lc2;m z9axsB+4uG8wA&RO;n9Ej(Qa*u1_yyD4;)~x18i0n#oL`u9R1=-d-oIrTR)SP&E+s^#+6}((ANu85K0}db^&L&7W)GK?-vq{ zFo*n={oM#gWZ;vNFeFysT)w)iPhzD-E3dpl`X~jc6Xawu^a}Qj;=mDS(m&-So<+)- z$$t-O9j3!)l-7`1NnpC`ynMt|I8Fnn?Dz#+x6aGlhrSa;jxwYsk`R6_iZ6qj7Yp|*ofuQEj&1@~gxa*(0H{9$M8qz>sfZ-=rx z_ixL4*$#B4&(itiizMreo%yK0C@^c1hw0=dCejDJMi8K(g%mUmPp4(w*S!dYG2EGOL*oryERAq-DMsGUthz3% zqkmh9l!&2N?A{ZB;&yBu(c|;$%s$GjV(m5ICM~JzKWh3+ew`G5O9oI5Toi}^p^Z4H z#%;k+ruuU1TrxrHGW*;#{f~t(`I?<5J5F$8>2+?EGjERS5y7mZa5d?Fn@~Z#oT;Day=LZa1a2w^_4<2)FL2UsN{E4hGi-EE~ z>;VqaHB!!Y67!-kkM4o1-xP)H&-<;m@rn@l$f-hNZ1`mfNk@N^MwH`a7KC%Fm$$W4 zaTa8You)#ybxMCYQZ`>f5_!(ad-4Q@YpEi+R&L_8Hif)Pw>y1jXUJSVrCcc+a(FuM zEb?+TaG6l`ffYB#9#S4rPWA8JzfGTk8Jpa|$sK>4uTLS9__h--G-;7Ptk*0>Kvy$d zDyy#E&!`qg6?QZMwIPZ+_WoICgpJhSBTcuku(GEGqCkmRk1(cBxCMfjXl`L0>>@7; zmlu)eY1?e#9^FQtH+X`eJK)5|(8E~5cTx(8SS%CQ z)JLbFq3tUqGauTz%7an5nGQiYT{ICp8?wnD_LR=)Z!33V4`E?4EOvsrQ3W(M@L0j% z&k050Sc6}0d5(=Bn`6@oSA;{_W}&HFu6wEBgo}zD?(Jo`>w)Tlf^hZ1k9rN~9EVsW z@jU*_4VkrC1{!vJ)_KD8M-$E3ulKdAa@Rn+^@ghEFL4UIuEg{cEn!iXRcnABV*2_I zoa&0~v#Uw0P;?w-ecO{QWzVgBpSrGFJs@vKB6^Hes(gysMFNqHS{0&ol6V6Kv33WV zw_Ho8*XH4U9P+MZ0iS8D;~O?h$9-vXmtVug4O{AN-fH&GjL6{RWG%_Z%ag5!3dgEjx63+( zK|UknkOIqBj|>e(RGb7Jo6vIsH=sD#%{JX2A;9$emCYA^V4?!gr{U?9B~J z*iswO!%wK-T{IgczA|B^mriiys0vpTW}aBb%MN?kF9BQIFg0`P*+&R+z;UR6dM7xy z1!|I?hrAitVS$Sqjf;H1-x->{2;`B%D(q5sHxBaFfJ2^EL815Nl#8ZtZ{in8)iL7% znLOl>mQG2IkW*MHEE3rsDti2ubY(;tQW{)fy7)n#VNPi7@HbN8Yx#vf%ty}q%3-6D z&z8?5os#f)C$WH>`$?5BZ=c&2Jl!3VBgh$GSJXRB<6-GY{jPq|A&|GREg9EFU4SSQ z&}t;H|AtN?l=(-SgXHZVl{#hLgG*bi>YiYG|4_YtiNlQNe;&aI^(TPR-(sOC$) z5Yn0_ldEv=iTS4GTp(2K?5{q?FHCpGMhwvbO(D}O$5?6|ILoI#)#S+=)e~S<9W;h@ zY3Fs!r?q;txO95?zy;z}V#$ZM`B|8ul@SSp-}Ka&2yD`uuqbgh ze$w19wJ3-?^D=c0jFYYZ?HU@v)TVxXwipNC(lg@>9P4YWYw{>~b7K%)wux?O6nMlR zeHy30lPhUkbV95)D4gk5Cey4&tI6>P6xg*@!h10vQ^1qnznp>p9;Fqza`~1X?eq%g z@T*9bJ^R$FTqC>|-ZAQkvIh0dzlhI1^eDF->7de?7uoG*KlJLf8~^HWXyZ-89~er3 zF&?Ni%%1tZ28-Q?-^N+>+o;{2wa|#Bx&#t~Wd0_uP}xm2EGuUw;sP(rM{wtKh*j;5 zOl6CBA4oG?k zG`ELeVSGpYicSKaV9hYc8NCp1W($6CaE&@YTr={7pT{@m%pJV)lyX2E@GH9+ZCOFk zpA+km^mOVazm~QwS#)TC^hg%1A1ek8ROy06@aKMX4e`}x+QFLUMl$0!B;h(1Xgqi{ zNB!ND0&8^C_%^GOzGO;5GnJ93)s`p#;dFDfc5+{6bCT|;x~U#85L4^P&s3?b(9}r`?MhTOVnC-Ds`2; zpeV~_BfCOxKTyi^VBr707XGPx;0R6#UYVqQQUCQRRvlxU=&cBNsjSbkkHnhAS#LMe z_`C8a7jviuG+J=k*v#>72s$({41Q%7SoEw&$h8xaPx!aPmYlnvIyHg8hdZ}7J`3i~ zMpSa8+Wc41+*o2&IHk_UvsMDNlBq4(9o|nbdTGIsfc4MwhUeAI{Y~fY&Pf#ciaIu-H(!4R<#HgV~sX9b5qn+(sev?uMG?P06QIER)e zThh?55=({XP$Ura_Y;YWQP5uGV8*WH3UF&_#(2{V7joixcyefkYF$Ys{xq!{g_c!F zQrWRJfkL)b1)1)}BsQtK^Xl=4gm;Ex)!np5P95CiORX&1@|1PgDwCs5XAwI>$0jNdwnNcrdYCk&_-f&zHqIykrjifa5dCd^becQilY$gC( z3m4l;25_rO*C^G6?u?asQa1gaSnALB>ZZ4fjW`3X5Nz+P7;|y)RDM5m3fj;ML>N8u zs+6!PlMBtCd7T!~l$y|<<@Zvv=7Uh+$f*kG` z1Lo{GMhQ1I{x)ecaQ>B9zY#+godY*&OjXh^kSW7k&O!NIfRVU2|8ws z#N{!LhD>HSBcTdjr#r5!E5KE2+Yxq%LyPFq<^0??6_pRvwd0$#TB))r&L^3s)UEY8 z0d2&Pn$wxpZkT3gTHocp?ceg`dqnA2LhinEhU+yTVF}dKGZ@4 zr+q>Bz>s!B*1b?f%0vv5-FG&ZM6zqwBmczR4uiN>9+Z2$OxQi_n@~lr*I`~EN0(7F zzcYK3KY1su6F;h3agu+5Yh;bnARwmbOk$gRv1Nt1Q24x5>DM6?Jim2j5NxYmba=20 z^)4|`S9bS8Ds41K6L7RiTvBe4@o9uk0Hy791F)*7SLtd#W_z%>FJJ@XkN&VfT!B(J zzgoi5x0-@7p>Ya{e1F~04LDyaYV<^$C&-HuCpTytva;Ca@Zxc1eKM))tKS4B1(bWc zq65kdQqfq|C16(Ol8FJwwr#vcs=N_3UFRKV`-18{jF}}&9<~Ck%^_EWmIs=4I8|^P z+D&_m6I?1kw6c%b5T^Tu5yey^<8xB&YaC~dcAB4n*`bSgoCG5wCdM zVCr7m9yfIiI>i^?J4hET0;gcP_vZP>MJ$_txPsh-hZ;k*rj|n|6J88Z6R6w+b0$&K zwt$CR;!tBg94r*YuVovbuv)2%?#@Ba3w)G0zJHotEma5AOK+@-eWX-PMo%Whcj_Uz z)u{vq(;c+XL=Uq+jn-r8BBE0Tw_hc$*O^pPYqop7`<^p3z7{u5&!jXv08BDjO{vfX^&plC!zkoZ1d|HPSrpXr&}ag>)fC0J?PZx?x500S-4*6yFtzP&Vwwoei!74?4m zI7V3i;gNx(Jf=e#DQupY9I(8U?K)%482U2EnKnyuv76|WjNyA%jb3>hCqc4bx)${p z_{|g~#1_2jv|2=oI+%I#hoM^ht9XX+h^4;#08|!dT=S>L>1=%%u7mgGIu}9)2}s=P zd#UogizIz&`!>Ca$5otE;V&{8@oHpx z0E4HEDgy=)lWaQsnx7eW0^xNXvBCw3G&B>IXJ_J~kRS6a)dMUePi%_3qq$m)Z=jO_ z1=gSH3H@D+PSNboWEkY=o0af{GQznY+O_;9IS&q(wUklCFVWg9LAkCARm!L>j#Q2? z-kdCn3m!x^K7o2YWIq&(l(>Je`7Od?D?Q^)`FEB9!Q8;_VSk#t>})8;wZm4yLSj!p zh5gyBS5_!g3}5$&*qVr-x@N~$^zv1Y&?-_}BJjYc9gvBE?kK<>BHyqQ{<;a!iaJO)^RU6&b7X`0$&-5)8tZ0Y*UcFWjL(Ug%D|BfV!|_qg>0r6G!n^Q8%=iAa`=u~ z1;9~6=E&dYjy^k2b>lt^2}SLb_d;DiZG(pO@8IEIuBn=c$+i=OxCUOiKp8a%=uR!@ zlOZ@FAMU)HKcP0&dxbcu!c+-KQORjND!$1DK_|9()TcL`5;lmd~ z0C^y^Vo)an$Kr$`*qK4vf!GlgR&_u z(MV6VF(U){&2gD#-bkNFLAB;Rh#2=00b>+y&nM*^;^|{*%g3ad0gz`rdMEZ5%yIVi zH>7{Qziql^wRAhL-_;~$4G&pfNFKu}x-=N-qFl^?pqAp|(9a)w{_xiM_;D1}Ml?#B zj!U(5c5nK$n*AOAulLU)ib)%{E2knoBNMp1H1v-|7lSAO4(w4x4Xobl2_meSR-*~$`i z74Sb2>h&B+eSVh@a!^J={SPjKkTsdk;Q9}~V-ShPXK29T;z+-0NTet=8bMm^R?g+l zT*Yomg;o0K6^Zg#0>+GO?(H^-;Els7NLdK8)-VpZw*FR!o3Z$3$(6~DieAdkD zCpFcHdl<2sNMbG=J|3(J!8E^MmH8cNHP&!hxT@$65_7e%v1eb}W<^|neB$wxAxq$^ zlKLz}!GtiP7fi|oatA12qMNAoBT?X$zCFwwXaWOaz&6EG3}atOuEuD%56QtW>tci5 zOND|BdOn61%5_AEwec7pRE_!rgd)T^cK z$y=YN>J_+o(Dd61!~OaJpMw<-Y4EpAgDI()NsZtQE%rZVX{(Y3=d3+r^=Pa`0+p=r zCb_Y#a>Sjj$o9}INxYu-askzxBKqj^XWu_DokfaN&jsm82+oBIuk0nh|N2&gQi`Po z1yWbSI#XIDi%3&X!F$+|O)8xdUc{g+ADc~w`e3p|4+CvhA;x2aPnlJUU^&8VRx9-m zB$it!dSD^u5oTY$B_vwVjoEyJHCz>?2wsZQYjfCz`$V^!MjA2ox7{@xtWUThlZMLU zGh2%PjUwgJx6+iTJYKzG`CM+S_}Q7+TGo85g)z_?Qa-?xTxU^1Fw{am19_;>H#~$z zAU_aFxyxkuZHX{wfk1Br<%(ZLh~+E@eB;@|FEE$;k0Sp=J_IAx9vso^Ykqo}d@IdJ zV&N42rEdh6!d2ODP}Okdu?1%P-)w%zjMV$fYLU)U!K&b5nB4&7j)6^2CJjNb#s+JH zU%HmG)o=t>XR68PGS=5Y)oJRG{MTZ!<0Q8be{%!&w`nHW{(2tlsYphoq;-MF>+?i+ zw_SwWerRWuC9Ln+Y((WnOvkdTEX`-cl=}a9RS|V~?}npi!G>G>RKqgNP1jZEE>4e&z{*;l?p<{p z=}7%9kJ0+pB|R?q|W z7r2*)fU!;n{o=B^h`TLU>+J5j8FjLIhUPFXp+JgFI*;?gX|zMlnovh6nx)fUI)*sD z-5}MWnMw_f{yL*e1JGb~dMR+A@jIxZn|PEK;G0qdjJ%*{bRt^P70{qt(TlKQ=ab^) z;&rNP@4ThRE$wafhx*qNT`f-2(8L=B;98Nxi7hv<;iof$k_eWl0e0tJWn`7e$vB0L zMNnXzj8+87oXWsRHO}{)4af4`=;;nFf)%0mK=Q6^DP#L|v)L?^;x_U%{u$kss%S0VX}b8( zj?GU)yeXX~;inz&t&^wpHKkrr__mT!9E-r@#n2*iUuN;VCp>t07XulO&bla;z8uKA z^In-=WDI6wh{kH1HUb~#W*q2X@JA{T6G~G87fpL+Yq~&kmmNAJFgHY znH9@^xc^yH;^H9e!e*(FpVy$L^&14f11!e(YA4QZM1$Qp0x$lyTLd{Et+XHu`Wd^_ z(YX0Hs0W%O?ek6*hLWta2RwtooE#n#xFxO~Os_h|7c&m1_2Kx{Mi8`nc>ZRq`zii$ z?4+8k&pI8yo>^otDs^~#0A8< zKQa)a0)e@r^S!8ClXo-wZbbR%fdF2%OI{L$JcN&4?nC-1`zc3$nJPMlQb2w>zc15Y z*vPRbjPc)q`#IqUptoC$u$9g%H}c1QV{~o)9}`Lwbv-+N$K*M)p9yQm!+%2QMfyKM z8`VWRKNyt8ySTxWygR`gRoZG7Z1Ha^%gje##nw5KW^?o8C7`jCX|O)}57gL+)$E!C zSDhZM;T|%Lo!{n1Bix^v2o14QPJ=a`C?-yVZMZVA2gD?t804pYQr1T8DNV2US_$NE zd!EZa#=vx~d#HO-a>j`;;T5iIH;n7$}MKR$v$(9cFFsd6Xdy4}` z@8JXpMy7+eg1cBQ5yHyzQe!70sLFQTimAwdnkA#N7^DpGv?=>ud`3J5OI zTrK>IU5(_J7LuUTd1qviyJ&8TG06Ng`iKBJBxB72i=$6TL$ZNFJ+`@M!H=Hggag+f zZ>ki@-{7Hn@MF_b&&J_W&OcG{qeof8;H2xl z_C^unwN8i>9pY9q0ZIeq_xBu}H4x+JV5_h`GtUT1bE6R&QRNY=Rl3W9gpP2nN25uLix z?RfUvz$IJh)(#h%8ApNH&^bu2>k)hg0k=5Sw^W90dTiL1IV+$Cj|~2p!u`a%uEa?h zzZYiS_Dj-oyO0-_3DzWig5*|Fw>!%9F;dr=Rf!Fjy0C`y*4l4$44DR9Tz-bBfloD| zpM$XDO`3jCm;*o0;&Xfp%CLA8#n*sDsWy*hU(X=jp)Nw&BTIR%EUkE&9XU0zrQ_!q z6-yqSbtNr6BJuhS)p4V>v!<`s5`tP>YnWl zbiyw?XhP|_kLxDgIZhIx9|+LQM85urba^fo>m@mZr%(utkg&?Lw%_d z7P-Rf-FYz#LzfD!T2;@u)`FMln=WRgSO<{E$?Z%TF&iIom;F2kVOGrl__j6xy(4q2 ze9uI9sa%+L@VlzP`0w_&+J)`tbUaYa+iJryt6GG?7rp8&>Lw~M!0`b#A6Mb}gN?Y- zBIo}FAwb^0#zVQ8kEs7FC95!(?j_`17X?lk=gsF#qb@kV2CUDN)kd~8bkgkmAn0{e z1}I>#0{aU|bof-M{(dcC3V(wyV%r9Re4W=2aeD|7ODQoHG(~TO)X|(LYwJfLA*#HJ zV5VLoVVYOOl!iOrLKO1cF~~toKIDJH8zCcQ{pg#90dycABn3_0dSaZx<;|%BBl5(Z zDYkZG_r!-W+$&S58rKB!R8d26$OP#MA@QiqAx{QmJZzc_PqYdmi)(v@P;Hw>xZH-K zHR^WM910r=N)|Eq6d( zi9Kiw#mL6kwJ4>CqzPQACG&&nqjCX#F$M<#B|-yv$!Rjz7TvBoi`I_|bh{}tpx!x- zVbUfl=D@d3`MVOj;%J?KHE)@5-*Y2coiWjy&^jCVOn^do!d#BE z?Z4=kSQr$zLn>F&Nn4x?`^$~seFPi2K8TiuZ(MntVXGH~%FBoQ3^Y?ncVwIJ^GCbf zzF$7ja)>%7ObfB1sKY~6CHMcUdJZ)Fk{(LX6XADg<2BXYP;u?MN=o4@x95cnlVV*T z+ed}MfQBBdn5T2|lL?)}7MCLC(s*d}TONwpsO+m{WV})aQ+GamrKKZ2w%;o%Q~^E3 z2(c##pC}B;eBE={(`9t2Q{uH~q;fkO&#H@i@-ci<%F9yYfe?N%8R>K~eoE=MO3$-0 zoe{>K1|}AF#EJt<)fw_z$+n24q+Uj>U0)i5JlHjZd3a!gnPq~{V3%X#N29koB2eod zIU17%6udi?Xb)8CJd)UwvcEUBcPU$3uFv{lca%8}*BNS3sZa!j?QugWh_1Z`A1Bbol>v z{Cw)Rui&wBToQl#{ZnmLl9w)VX&`zm?@d(YiRWe=ba}?O>0m0?Bf>igxDb25Y`zrM zf8s)Etk~6q>L)OZz?M_ddyaJ{*H3oicEBZ8*usi$d?_&$ zrWX@OA<9cN85cghk!Vv^57VoDgC5S;=XHZG!HP!4WKoDh9yV-nxzSWGkxWWY;Ar|B zg%<T3ui!Ar5-3Cwq2bEde63nN2VNDJss z)z}Diw`XQhrbb7I2%Jr~fGB@}Unn_pprS^WQrGCoLQ$Zo|(pvIV&nY!52e)jU5TL-7r8($y8C2N6NfnY%x7-{c%?@N{gRD$vM zPQw`!!mBTvu0(bR`{y2WBW^^R&P*u0SGog#2Dz7%x4v7a#Er1 z+wXaKw)X~H8SrD#$2~3@S`J~)JV}I6MIJ~le5u>+nrA3 zI!(JTdVuB0F-aa^t4i?7qVfI{yfMZwJY=91j@9CEX}MgSrj{J5Fay0pP%fHlmV{Rq zUqN8d!?S&#r){_}?~Cwk==bewA>Uqq5ne33N?hJ1tVJBbQ7@PX4304 zW5F3N^#dSL$TkgouF4E3cr1Q1QoFNzRkRilT=I@~On}FgDvbKSxVEl}m^Q?{&?F6W zW$+sPB$OHxH>4-X&}PS6|{N~!zp>gi?z za*-~eLBjN+vj34f;ZjRz-uxkeHUIMBqXu|lNB7$c<(_&=VXvT}EJC65VSs8g1=p!D zmqEa83_N?2-a4a46177V-LdI^www}83q92RG~S|wAx}Re)@TdYbuA6sQGju>y0LV5 z3?r2B4x2G2h@y1-z3k3)5=N--&SUSHRW*hL%)C;Fan@{brA~Oq%`=#+t>TsYwyYwv za=@BmY1KccufL-TciPE}Pi1s{(;|f|nIWpFC5m#TO8I|&7Ve}Z!@@r3{-d$GHG|vE zdL@~OI8zcR93FCCz`bWglm`#-fk6ib>$DwkV%y?fDrizz$c0~r=m_Zn z#pKbZVO&r)>J)!Gd`^#UR0LW<4pn!-YbZ6Zz@U~4&A^|Bbvy{CkMi_Qm;OpXf=9>j z-93R8o>ioI?x&6nH8tdsfCx2tNMWnJhqz!}1SYwn5R~ZI*e!N|0O=)I80DVO$f`r{ zNTlIUPc0ww;t8P%{kjx_Z^5Sc7XX!pQ(=WiDj+M|Ca9kfoP#On?3s%By1p{}?fs-E zCIUkc7wFYYhSJN3+aEVXocZBv(TMt2r-S_qKJy8iDcb{X1q8@K^@hvxh|6j+LW5@z z$`g%zoMmcI5{7;|bBOHqLwXpP-#CDy*xIZ9N>L}R8tiT?kfD0Zu7TJ;VHTYk#vmAitk3+#XFC_pm5>|7z1wVDVRO3EhdK zRU{vGUA*}KgRYzzff1fVdRXq*gphAQMe95IxA9liVEL7`4+cFjiFcui_ge}v6$sIY zhb_aSU&F1MB;7_}ArWOUE;$*{_1G2`7z6u>;W4RqCj7c~da}DKKAAR#NK;dWy0on^ zTgfG!0=yytx0?%bS>{DgV*FC$d(Z2k*oMu_=3fCiIn&dBe*4Yy$fHhskGV zTHX+JD)ZO)n}3lvbv1?nw>0%cn9z$F=G21+vxxxoKfxGCY>$voh==`fM?xpbWnn1tr)$70x%- zHE5S@aQwD@@MY~sZN(z;d7?676d4GtKb|eU;5&18`DBqYj6>X}uF*9UawMgF_^<_L zr6#T!`u!TOD8V-$doyZ&_SMSc?SGX}R__SUNj((2rR3PQmfn}z&v6p1GsBfCt_!?r zpcx9OSG_I{H%zl9DH#~GHx#GW3VgI_5B6#J46YS-&2p#tmuPhE@u^&!aOVEidwOwS z-hVADrt^5(wW6ttfbQSxa6nK(VXfdwzB`!Ew|Q=#t)!R=5P~gj7f8l}8NthmlUCic3+VLsmD#je$v-Rvtie`giB2HAO{XrvRMRx@di-8CR0Sw8( zPp<<>Y7eworbH6%7@ON?P;6->^mYgd+E~Gh5Bfq-(lPvp^y(wD=J)R)&7r7UPV~p4 z$euW17(r<6^-n01 z1yu{VH8+U2Oy1kIDCYG5ScfW%wXfi1uLLSn)0%b12Kc14ZrH$*{1B=xEVAaJ&y$#~ z&%9m2Bvb_Iu+-B3)T%+d?O+~qxpL@y8)DB%WCR=U%l5xV^e$ClI+E3BjMyQ)b1^>) zVoF0QlxYok##lzKh5-(DIL!S{I^a%tM11>k?mv{>z0x5Y^MW%HJe*Cf5cB*`QM~65p6&JCB#Gi2G-R*R#a$wJsX5GH%pJZF;hyxkdjN3>g=xrn z@APn1Ot2IyLGk>ZAl7uV1H#6W9Tzs9d4(mZC$0AA@gma6v+jWVbOCsZ4KB!1rVrN( zHge%A<%PJX*F-;D41ilh!g;sGP=upHxer<#-)ggTy|dj_elYLu-3WoX+$V(Qq1v_H zoGY)!WxJj1&>}6dRCyXGOii#WOI5dmF=%ykQ zz-~y(oE)S>f{Ul$=Bsrp{V0IiqU2}cmT!>fN3_<3IzE^AarVicpbI1;K+u}})Osmq zwtOmyG=F8uG@cG_^B}-{MAjy@~gR7X9xlWar@EkTH#B^!de@dfH zdAtyiY0ka9{_=oatBR$Doxv0XHTQx1eOsyhURe(sY2F5@5`5ot+8jdLwF?w#3|*9q z@vO@SWm0jZ?`0`Uq3my)=OE=7OB?TYlc1ksu)DAmFTEE|u$8E7Gj;yB3nYD-^{@zz zpjJzzr%^SlN}6*{SjsPFtW(7C8nZL`o#7T7RKPb#%GF78grx~cldQKB4Y6%5hBLc%DV!f&#i%H5XT}gVcg+22rtS4zr=PxDN znbE4d*{>86`#)rWt5kq(4f3ZjLk_qzz7yz;RY<4tq(3d$A7%3c9G5&oV_~yYO4$A; z=y-a!ynU88-2qMcKJi0L6CW`lbh?ERSTwNBu#|@E6w@AjB zH0I8=tU=|N{y7mvb zJ%6S;p;o3Wybr`7yY70f{6jcTl=T^@xkzC7#^X1Q`D$OiIg+JrCl@3o$#z9Wcqd^Z z|6Eoec7l8rmOvzFRr$n@!F)>2Uq-leN{1cVQ{BY{y9)4-hPYR1dM>3|pM*#3ZkF%1 zR*wUHfP$y;_g8w&7;*dB2Ko9&z%IQ084RU0Qm`wq0oyyd0Pd8y39&n>jK z&vhvM3K%4G5;|*uTQC6$T)_AGN>0!&FxU<^l`;XC{3=svwUXS2n~X9(4lC$FUJR2c z-0rGBY;NFD)Wv_FcF}+eGMcde<_Ic`{m*8(3lrT1}J%} z*h>=0jQH9f2Z^bsMq@zSfw)u$G!3gWE{mta;YCjoDXL@_x1~WfSnmq zs&#=Jogj30& zXcU&zIUL7cEJXXU*2lEJk!A?LkaXH;CT^CuHCc=0Q=n7dtu|0zBSrEGKzU)JA4&`o zhI*xfrhnf3oUJ54GQLKKaY7pf&j@taTMCHdtm%pZHzjp688W@k$S4ixTZ`2FW3qyW zQe~9}d(>U}EWdVXux>Xffd+8bYrK5^p(6?Z$OwpWALI~k#%6XEr%pe@_IadKlNW^k z=5*GZ{y5l3?vg@&KAzjKPTfzkqIdidov$U%X zg(F#pen=UD3DAssRs?UF>qQB|>v<*b)2NRsIn9?a>rsSu21x@~@ zraDD#e8tQM-x#c#ZKdxJk26Al@3vqA32F<=QD48Q3iqn%0-Mu|GQetCk^Iw6=M2~n z&frOpSoIJl==z>w_}w=6@+_W^pSqC@4W{{xoewEgn1rdY<@o{^=1w_bky$78RGBq? zbwyk^XV^qpB{|SQ+(p5QRwl11TRMEyi3N+B8a?<&$7xfA*Y_%qoEd-YPSTwGxA#un z?Vf5`50+_V%^7PP%??IGn z&>HVA?%JxaJ1dl@anFYq9%WZ0_viRZ-#Y-+B51}nG=oqP6u{Wk)p zuA@x_Npb|ja*lrw1_ArzZvSMN+WXJi#L|0?@b+wrdWa_&pd&o8 z24XyYvC z67uK|FXAe0WCy&iD7a4D9-SuaUN(-w%&!?7)kknqhNG_31v+{6E#<^%%t*=) zSO%+wDezn^mKDu}fOz=O>M+bIvXJsmBaLLb!y3G+n(E#X;$FR*%+S6g#F6NWLpew&gKCjJ?O|9CE6BU702 zLp%gr!fup(w4GljrO!8tKR-4SYxQziG=q$_&dajim`H+&;kIbJ}5(*r$y%-Y#2aH1&G_5 zYeEC!C>^Dt`yL+sx0pS=2d64ZeDf1OnuGLNxn$FBhju6RVft=efSwc3*KXy&OlqHm zHFMlFVORn5jKF$TP46znL~M2ix>=5L2AacfxWO&0GxmSdvcWs8PR&X{G^=~J%HS6| z{S%%E^v^IhGWO=kr3*{{AvNjic6jzOy0yYEabK&Oc8oRf^DUJ+#K`z8%)Z}X+tS;k zI#DKFdvapXLjjKrQV>+p@^}C^7pN=Pf3Ip^ZR^y1_BF?M=H#`ae2?&H?{?Z zDE%E>I=CB$RF%{o{+QfRlrs(Sf;gt^dU5W1}n`ssz5z9=4 zcCwNIW(DzYiD0~8mIbGUrDGnd4v8xtEScnmt&*^s^#N}y$12Hz2C}U^ls!S#?79ez zdnwZ-))WN}vHi|c90=}{ZI<||6Dcx8J1Yh$yxWSgV+3JybKY|ByPKY0;yHBzc|~2* zn)#a;3o>wQ3ibO_g35^kWn8;(KH52(iVIikXB>4682z+KAq5k7&`|@O)-t9%DmdOd zW?^sD9@yvhjvZVT?9nozG|U(N1)H zD#5%jJ|0f3g!*U%#g!!|z5kmVS0rG*Vx^K0l@@SDFl3hs|Ayt*y3a|{w`Bi0Dss(= zaVX!3!5!}LP}xHCyq&@(5V@Mx8<47@c%P!I4I(Wza)d{$P_XCPkayxVSJRyk9PWPV zck^pff?jr0s?_yY*6~zbLWhUu(?111D#tj7!?pmp!{squ;lZ82SoM8mSzb%G6Or{2 z(a&0QKzb^{nRx3d0$w|Ux-@!>Me7Eb8`g*OLm=*<$qAJcY14Je zhaXdvjP?F7ArR2OH5G~Oz4vHiuS8g+k z6OGuCjYZRGl6xFk*}{7KMoGXnLhxuG<_!o_r?lIL1=3-IZVSS>>u#X<#vNdNH1zhe zBsKlO;b&OQ!<2LrxH2kIIY#qSjUCOQS)Ys%&NzGVh=R}|vJ(4Q#aFg^*lb}*-F=lw zb4WZ87M-(;OoQw6w3p=#cn+C6BNlB^@qYGHwN!N_AepJ+i4!h7_fg#Jqh;7P0R7NY9O*AmJ*8x^Hdr?TU!YD&4Q)3J zBV`L7yAp->-!f1Y{G@|_$;M;90TttJnsCd~V52;2dPw=4KrlsK4a4igA#fM~#TNId z@ZZQdXH&+8R~FDlysXu=yw{(>{&__}NGw7WlkraqMcgWj0VII%^&g_>Ou`j` z!%{)3M=O!biZaUf_)U{7RDD9-kabzM%sfw4e3J<$5J8Hwm7RLM-Vx8*EKff@$A~kE zb`84)|K8~%ecf*SBkkehpt4#xX@xZ}<%n%Cc87Qv1=o;(#hxz!MF8uYHLZO4k0%d} zc&%t|^EP1om7QCUUvgGCc`*qfKKI$?xdIRVQVPzPF#w{MrbnzF_)+y;Z#a!S?}BJN zJL<8g7DAF4`9TBBT%7pqZk0D!mVV=`YPA}rAOmoOwo@k32_o71t3cQhix@2yY_X8*tXVxfN~^+kKDxWPgukf zeqV95w~I4xmRk#TCmgk1a9>jBOZH|s!+yIBT{lE|!}GZ;1xYaa4ponQ;;GH5#%cx8 zEZo1NtahbYO`a2m+rU;u2!kOvZ%b`85?QaYx1QLVBkk0m;GGYE(3;)iv^1t&oOWE$ zs#nuSt{HeEPKJdp!~~WLU6)%x71Wft5%AW+3S=_ukBm+)Piew>ME*o$z>s4^DrTR>%#t9w*Asu!xuH zwF~_OFgAO~8p@m%9FBcn?*N${Swx`U348z~pFxf1Uj6J?8hAIwBx}6ua{>5$J zJ-1#m-dPy3=P|Bq#X;?JQpGf&uwf$)z$e0ez9Q~jV98W-hGQ#c@A%jD-Iv5BmSj3y z*U-=A-bpZ1m_p)!j)u#wG+##MISCXeU*`7z!hJc_h!_exe=i<` zz?f0TN1TkR2cRJ>OH088hJSEpT#6WL=?M?I`&4292GX_{CZcKu`F$M6(vCbfxKiNZ zM)dJAAlWsMD|~OoCSSp4qfDKnIN|@|A&07j1~KwH&)< zEQ5DrXyYfzg{mhb-gPJ|yBBY*)DiGX(UFkkUr z`}TyX#q!s+QT)hxEw#FGr$p-aLYXr-hfC8g$MBv!)^FWgy=e=ex>^TKP==H=;mQD z?Tud-i^jomaeU#96pP*Ox87pj{nTwYczDV1u5x^lBDOt9J_T$;5Gn`Ib3g{ci4kbb zx-BiHFP7o|bF;aq)wn`r=8iH>kv#9c0Kj9Sv-;3)m!~@kvbvu_z2JMWsS&%)1o3IV zkA*yzJTZ+U0qRmt>`^j(z^@>whK-*-_z+pNk%Q=LI?hd)*gca2SQ15csiGc6c}Lsf z)1)SCx-H|V@F0WDExp{3(9tUVle5>EEyCiuA*o&s6 z-rJ)H&?xmb<05wc&^p{iOs&cO2#yk0Hrjhw#oEI7ao*huWaK~ZuxX^nO9bD5$2rhR zZm2&@xv0n^#SMMxf=9{T%)Kmv)MC>2LV}blkoepfjX2qGGoI5kLNah+nE^-5I%JVi^ zr*#LmLUM!gdTf|pyex!d8rRfKKJ#h%6aA~$KN>=V9g;o(%0kl(n!JClDMSBHituAJ zyfa9G&#jr@wy4@-1-0}-Dg*SyKA=eChpIQ&AVQwy8jc5kuBHK)bebASXPr#-#fo=qXMn4SKlp*mu zYoXm=OwRR4vQja7?S}Pq51(vpRkh|nTkTk|&6!JAt#naO-D96C*}AjQuaZ85?A_P9 z^fH4ZmN-+JY$NPlWd->*9hMNSeG##~bS?G+@7RVjah}DvjL<^lWyCr5n%PLp`R&tw z0t*rEy+oK8wg8i}siJ$VSpR_xFxatCkWZXMc8aA=E9BtwO;hx??4V0V&ODI@4o}F? zM8KhpK!MC|OiHLP+E51Tc?r$u!9`NK5)uBz;E$zu$&!?D8|()aC``w*BJo9D_CZQ5 z&s@kTiO+#KXSUgJKT63=PpC9$=0C%h)~LNiM)S*vVaDWms#M07&|&ms9YOf?5-lEC z_V=}aI{^@=21~SPFvsO))HiATOSA@)`*;EiF0^%EEG`?kG{l_EA|wjjYPp;<9!7%X z%|CN}UI33*S))-e!$Y;mb;W~~O8D&j;m4G`p`*~PvOui|!~Bs1H~0`^?z}zJ?A#~y zHGwjbgA{QW+8?{6V^x1{d~0%SLO_i(t7|58*)*Q*_Ryu|eOoTsz4*y4Enx|!4!_FE zdy-VJGNonfTBUX#^(x1t0eT#E22q!N;$2%CjO7i^mSkwsy6D|kFOG!)q^ZtP2S8F@;w#B+kS|e zcEAy}fqv%^T-ssi$h@l&i36pQmePKtaavWS9k>_>)#xb5OlW69J~T!=rmO{e1EjaZ z&;A1Yj#j?n0eK*xr3(X6cTrS&qt3>e_n{A?$oMHRI5P$jEUu$wz z8NA$u6nsT>TJtyg%d5Fxv}qE-!1t5d703i98`7QFg({D7BGZKb`PmI&c5MaVRS{^Y*aVtt8Ru6E5@30N z)vH$PjOASrO29y+D|YaYA+U8X1>cAEnCjLHIdqNOXv-vrD$iRMM`AkO;%FHZVuAqx z4E1+P*OHG@bJ%N+ezw1w>C?z+WvRGfpq2=q8tS5GViBZ8?frLux#>6>A;iKHz@@gx ztp^wK2oOcCZSzAVpZD`*(;|64)ks{neTZd2PDOp@KE6Sj14tV`JBa$0gke^E7{itr zLUxO8CM2Y!Fpd2-2v(+6X_5b4dWL_`7V6Lt{o#R zL$o0``#Lp?IJ~t-@ZO4eQI`7h?eQ4Z zo2Ai074&xc0Il5<$=@v|GNL!R6gPWJpt$`FaH17$0>3WsIJbSixBPc0a&>uGmH{zf zoiz9AH}0{Q0L%RaUjfWkq#$Z$+uV4^TaO7m1y_jcR2j%^JdZH8#Sb2bq`$JxPm9Vj z#ZRQ#TwV+v(o(}|uesrdK{R67IuWiRmEm6L&kP}EeY_ZKD;SHFbz2)G!~x~n=!Nhb ziHNM(tf7&DtP)aqsuluTdt9CgRvWkDYx!_9?&q<~XFUUh9n80jgKzQu*InIISiOuW zM`@N__n^v)-+J(qbkW*gYIF?OoP0H%f>m)w0^}00k%QYRuy|{eW-?ya&V^WEDIq?^ z{Ocs&J3HGwC_RT#FPe`e#tG5-H8FOT*y#Xf&7gQkwQ&7OJIl=(&yJmlWlgtL3{50` zn_~J1Cfie+EBLz8`azy~kR;k$^DeUY3wStJNw17X)tqI7K(Gp9k&eW+ozSkNpdEg5 z!>47EFSOtoWDog_Ld}|G){4S`Cj7SXj72ls>OBqI7eb1umP12fLj2!YJokg&^F%$N zg3D{8y72aylX3W%nM=t>88ujjx^0b};4J$v2l7G$U4I^Gh;X!fM$me_MQ8`p5lm6& ze^KJNP%!mYpM86@#wxDtq0%3m$vWhK!A!4wHy~=0Ul(bVII!l3H@a6FmTg|9pEf2< zOt)QP^&PqlXYOLW1jy|uR#8&6?&gFhHiv8liHaBagv~(6riYaA3yW@tgS0dlZWnIu z&m0}IzYesT;1pWd&AL!UVCx8VOl3{7OWCzH0Wg2wV{!YiA_^k$%^RfChPWv7{Jm|; z?bKDmRQj31IRn{~SoCe8$8~Y}4^Y);&V|^-ZJbrM1ndd3-}GnaXl|58FprndmApMo zuf461;ZJ|d+R}-#R3#m4xA*;M0uZAZ*q>MOhtX?|i+YlfI1$_>LH?p7martH5ynPY zQ!{BredRsADMl~Py%&O%UMMniNNz;kn&Q3DhaFan7u`&#Fv?`?UJNbQNyEkaLStpN zl^iSGbb_E(8t)})`goI4U;dW$9RhQQNK%CM7!q85a#fF9IEt3&Qa!x$2&{Yi$!PTm?Zwx`9Zzv3bVEh6aK>L1F3$b>L!d=J0}ztFp8=X zc|De9jPe^w{^#{(Gk-vHc@m5xc*V*MwCcBF=DLPkCxeeSgZT}|05lKjG#_$ffw%v! ze7$7nK%LG%CgE6f{{(9Q|AnEG40^4iW)w>_QU4vM6h>D3RC6Qn^Zo0MjeXE;Xc+P8 zYDvhN(~sx|o(m2vm$H7$6380OVLDIY3%7{_c_8CAL&J(n)9SF+LzBPcQY9ia7^Zs7 zb@s8RtkanoB(1XTO*MRZAs2_PggTo`TY*gu+Lx%w7gDMl%Kr_xb4!)uXc1IO%`v&- z#MV*!WoUw)fl2)6pouQF&7tecj8J`hZl~)4C4EtNsZvW)OyA*U=czP!3d&RBEEaCX zl+K8M+*kI^Hqf}Ag^$p_&C~!r%sdyCZAM-*vvV^0X|q%{;>uy+>MqA0bGbY@G+4A( zgAsTFF5>*TcQ7@as@uPe6aIuldLu<`_j!7xYJ_=IVR~K?|BuN=f&L6X6oMR>E6g{R z@|BC8AqBDmyLCV8QLyqXi8T1GF?wUR5w^L0t-peGnyU6tQmQ+=eG}&oZWmz_QgE+& zx2v7&#A|UWA^MX&HiE5>-j$b9RN*3E+9O+?hNUCK#H3)tdL-4nw$H$SXlT9nYyeu*U2kDZ#P$ChZ?+%cmja{KQM6y)|Ox&uB{ zdP!2{4D}WT@Cwj(O88@$t&75AWW8&B3z@w1PWV(uY0Cpa0CC4)zy-Lkou(co?z1^` zv;esYpfCm1XOMCr82R)i)b2=kbHbQKi(bmw5Po>F@AIyGF=lX+Gv2IdTLff?BBm48 zA$_O)(7tusENLkxT2?9sev6JtaXd{O4Zi2Ga`oX8uc$kkFDL-uSU27#8po=$99!h$ z5S$r&s>FeBoYlc@04V8uxh?C4E?ac-sE1>4 zD=NBKj^DMk50SyVK8U0^HyWXa85@&VRX*%54P@4YJS~X1FOXa7qmY7(L@G2-X+zN{ zJ~ogqYe;6c3l91niG_QP_@m~Z`M_I4UyiNpMzpXc;A#7a^-oF3gvVSJsn>gtI@$f) zSt;QZpSGY5XD75Dhmf+;UJ^#ZC!?$SHA%OX<`NG=2-8ARw>czI?}2RHr!E1hW_tsu zWAR#PHCQ=VXS0!REpQ(3)|#sTZF1Rs?|r_sL7B;)<|KIXsw^k~eX|hsf4#YWSNd|g z5G-#kIArcA{$(&RuNsPY63JaE-qRlNi_nF~{&wxnyK8+nbnXvZs)9BJxTpbaIK z5ChX4r)`Pgo3%u1^e`JanF)vc)OA);@U}3#Xm~Z%xm?;#sDuT=X+Uz^Rfx`j;PWU@ zX@pjQ`G1zG_T&1V#kw*mFO;UYZ*(F(QmXhnprZo{T~*B5&A$Od8dJ_^*rqWDT+VMI zOnA?c;BdLwzX5e7GI-) zXJ8Jb^UhoDuYr`nD}DYoLCH60bF}l9bA&D7tPI=M2@q^z$gdnKW2bagvWm33j4XKR zcvm96rC+Bhe-z-5pK$^f2TcM1(OYt&1<)S#wDsHZLYE0Ax^N-JQ5K`eiuu>iiSLKK zd}f|eVsw8^3UJPn#D(=X+d*HeTx>ic((i7lZv5QWbxXR+7e8wLXz?@mwcB+qr4Y== zOWX_z?t}=e#28Q4|7*aX3Cl&yneUlfM%qs}Xk#5pFINATUleebt(wArz(qNLFc$be z{F$bB;?|fkMi+}f@n$n8>W8g&S7IOz2W_IosI#5Z8bx-rU22E z8-9zPu^9{Pi4bmT00~2C2ekEn`XF|)w;7BAIH*tpOxxJFM?aclHqN@NbMDL5@yQ6r zQVG)-3ZTA`qSKjJ7y{l_gR<8tx@9I5aGdtXG}vpy?qzEA0E%TIsHF@Lg&|Oz9k+5N z6@TLjQX=v4iPIH%2M)RtW zZ(40HYiDnjLYzrc1hCJM5!g{h1~pg{UN{~BAh#vygl9U@n|1@dBvDsbG5XPFW%uA5 zSL{|1tfY3aV|;lnu`DbcxyCdP<>`cm$C}4G7zluJAC;;Q?a`D6y5p^S$6Sb;gA{tT z;zDv3+nP_ls4Nb)fSwpvo>^f&UQ9rruP=%)L1YY~azS7!N9z>p&5zzneH&g5SoDYG z{rJp;=WW3r@0iym;=lJ+z?y+J0?q~yx??~WY4Onucm7zxpB(aWpv>V6a;$YC8tK^z zTw9$=UD{OMlVO$jY`-nt@|UrHHv(hHI{W28O~IlWfaU`4Bv^-E5W%d&@Js(J<-7>~`PW1H0QV@!Hzn;8@}K!2FLRynR(NUbAP71huiX`yw=ruC<8p zi(s~N*U*9DJ19{TM!DMlL()Geml)f-_0dr%G`fKSz|nkAVh>K=Hp^z1BLcJ_3Mzkv zLZUYvjlo$8x7PiPnE-vDV1v~C`<0W^wra%VlbK6z!k0n)#_ZrX7MCC)LKapGac^jr zvyFKZEI)ysdNZsaBTy;S!k{=&^VsvJ1vL>KIxIz|_?!9OGfIxfOYl_B#;HczAzwGE zNDiPYJ~)r;7=cMY9AjbRR}(aJN$yrSiGvqE&B7>kY+@G;QX|H$98U)N zTn_g4oB1xm?nH+Rt4^{MN1?iaP}-3yK`{fvUh+%z2(!j>C$l)R}Z4a&TC^WkEi135-O}719~y6ldr~_|naHXl!X2mI^Jt~&?xKY5KGwf^|AtN@CbqtkEW0NRgFV8yJzQ|y~at{tJS zq#4mC`Da}C{uJ|Y_ZEmOp}BNhf8(JbkaU#{x#k=1OG6~8@Dehy_91K)1f|=z1!x-#y{Ym!3@y_1*t2&`mxgs1#?xMA=}8W;sJP1#>n!r}qGRs!EE z)XFvUM`I?X2*NGee?D6uC=K`xl8kva(B;N+G-+Q%TK$<+GZAhwi-2N6om*;VS{zR7 zwEjvgnBp;)r}h1)5cmGv={9hz-D!buqdZK7S~lY?gqMC?vW8LG=XM?5K+@T@ps;u1 zOf3i8`PrV$l%2yDYE}T833Oyiv(kRfw>4MptC!i668ox(i$s*xOSbbV^9CtTI;{BU33 z_1SxmL*>eH;D=pnK=Q*5`v|Rm=`t%;=!v4t>~ZfNpiK4A2&n~$i2X4Qt?r+()g3gj z2u-N!uU@7FhHE|jHk0=`O&sjixkQ_zOP%s6u!3nLzMs=hIsJR|Ifr!6Y5-3_u)iB| z0=rX13M5M>(FLML0HA1xiG3M=mh)DWFz@2(VrVYqoo*~Pvb>g#R~(CS#a!*L%-v@PmXR6Td|O>a4|_*o)qO}w8<-ggjwt-oCbZjASIaYj(dNaNuH=WW(I&cU z$(y&}mk{&)+3s!lx6N$Vv&Hpb^tAw>WdV%yd2X#ZnD*y8CI|Pzq?IGs604b+6m>c7 znV6TDD^mW|5#RFsu3ePn@%6|(SYZBJHpl^JGa_X991G>n$SvBfCHmp%Qf`NHsfP~? zGMjfVtZSNLWhXBpq~wGf7HRxR1UnB>IwF+kcK?Ix{Xa1<*IDh7A1sh5VrAbEa%VC` zi+vWXZba8$p}psmEC7^?nKaH9W?!#zqjMmRBoQDA5^UhETP8GYzQj%Hc{HeG3M8=~41DKM3R&}X zYJODp_bU^jU>5=W6M?hFwu08yjMKWb4~CTU0E!OQCe!RwQh93{PNd*g&;Yrs9M4eA zCyh2=&(7&NDt0)rgxYQQPso)Xi#ia+oa}Uj%_R@gN_>IQQ{+pBk#=wMk)sw$zRVV( zE59V=J{6ccw3t*xKIC2vNFq`rFY2CB9m-t-fQF#P*2u?iNJR5`xg9M+){j^rX^aN8 zx!sqWYhO1fpCNOLQDcimTq1A+oHIzA1gW%bFH+Oxw)wH0hY{_wP+Tgkp@pR$mO!(gAO$Aku4hsM3J=m9zJN9&iGGaGXOx>gWfd#Xh@~c2pB@3+Ajx#^h zT@+}4<6n6~A#@ca3bHg=-!T3ZaAi(ls~(dsNi9WL^voE(Ds1;`N9spWbS{`obIA0L zKw37Wcqd|i+$)*C&-hy(a%a#ZT178#rmUx`MwqIW~H|pROYVQ#$%b2!0p=8o73N*5QPb{hC*eis!9ytT)OUdB`ZDHZ(8TcPTr zz3+>Oj@Z8m_x%$n2Tf!sBYPy=$-=j_+C=$dGe=rN8=n|8nqcjgS5~< ztC=JUu6Q}_5v7X7UA(GpFQmWi&tjG%@l?(d2iXJmvM0!4b#RlP0anzLSmh^rDagpw zzSE)e#$854T=p{O8epQc8v7MA1>_~fkio((@o{+!MDxY zGP4%eoF{0wtxKidRAf=<>5-y0!iv)bOgtWny;pUU?nC4=T&-%7{oEmjITzdU`6_md z=EMPsvf+C`%yE6(;JKK3nccAuGjTeE!0tLGfMkq1>H6R znz}1{8qo`H?j1w0hq)y5X@NM?^zt1&U@AEm7t5_b-6PWmwFQFAKkW5?M-0;Be0ChW zI!vA+c;2nOUIZq^R06*|P(vscwe4!F{Y+CS3rLr% zEhWd;*cD#k9~~C-p~y!KQX3~qaZ zu@0_=zQO$F??rltw$U5Sv-&0eK+vRMt;zF_TOawRJ#+sP*fRX z0jqb9)Na7@=JDkld;tcNG8-x|o>&6|@joXuidt|1+c_Iy@joNm#o2Ep0No^vb!%o%v*1yJE-7;_6R}LbOpGZXA zMBRq+b0xHYN7X0-vGkjX%RgH{Ru*v79(k`r9FKe#R-|mrc4AgxbR(fcq%a&De$?YO zgD4%ZS5BJTJO{-ZOL`y#M6@DD z%QRK*fO|2QgQ=&7h^Vp+R_2U9FyYq1+Z`OSkv`6qFvBC5W!%HkjpolH9`_{DQVI6JX$g4b- z8Qcl)0KrJI_0DP=Oj^K^&v&=2AT2KloZlGpmJVh6Hk}>W17*NwvGo#t_H|5YQ^4%Q zlZGU_nmdOR+i<=+$J^{2c zElL@_F;2BrA2nwGI&MUrL?8x8-42#uQS;mAZfPy0-DV70!{qbIHATXFv8JMAed=Ya z-kkd2d?s|mkW3%sf|aA}^EK2+lK>7;ep84HcD<=Cg@BKXjmYwC)vkS@*s)Rs7%PZA zt)RZ)jzi31m$ETxl<*=M9~o4o3EcBY>6Oscg?aAwP5_w--DfDSG$NDLR|8 z`KKQp)s14>21E{*!%sxCm$r}lj&F1y2}kLX26%Z}7!S7H6)@DiUormlenPzUdA;G=oX3r*9*`B2@$Ig>?G7$COc*Ba4DLH{e6RjQr>#WtN%M z8^KxZ6Q+Nb*S)?0S4pwy6DNO9sd_x2_`ld-P}Zpz+U0_codBP4V^vzZKf&&9A-+EXLyK(LD?sIXw1Nut@5$60epVDp0TDgS1H?Bn zDftzx0w7WE)W65IbIt4ZDSH>H+G%l@i2E9emTHIkKrFYO!XoFSmcH21SD8Be#>fLq zQ(q*?q;*H*R!iG!koWk@bS9F!pEMjeQ(pLUmKWvO^p-9$I&=Ui@mr@5v+`gu1m5># zjmiN|7jF>mCk znBjFH=njz+A?e)t%47ZgnwF1t3k*Ix!D7EC@2EGLrs5X9l=B`ez>?>tUVHUYHoG|U zqN+AMnBsEMFiv-$AHf1>vx-*`2VR?5D{-Ygg(93Zcl`o$5Y5xd+r8Ob=u9YWgMvMA zuNNK*n`hayw%vG)1t;2zAh~jT&w6FG%CNUdH2+ZI31V*GLq?$1a_M9%Tn0okAfTZ{ zW(G@_@$-=cl-(9O78Zj(LMF1G>GG9cn__Q1?Dd3F;n6RmW2cMFD~iWClZKBhKYgaB zQK6xDN|BV5ZOJA03huYjH*&D(%8B(@&Fz~0c#U4i|Bm6o_+?=7#iV|;RAM*jAis+W zc+_L@l?idaZ6SnE5S2xRiLIWfj;A`_OhZmVL_5OoM9{}YeD6hCC--rSknu&?$>Vo? zI(te&gi!k96LJ25C}USjAD3Mdu5x+SVLURiQVODZXFL!NJMeY18jJ{PV{1rTN0~dS z%FnvXFvI}^=QDvf1c{pM%uo z9t3XTQ=ocv#u5jp>_CyB^}q~)jI?y zas~tg&n8ut^~UzF?!uv`(aDDK@-;K79!v8z$6i814El78j7WrqgLeA!yQxzsvTB#jxoC@M>#!<(VQsUhn&LAjCn(@;;Z0vPkmq7p_|$n zhyp~sQ^9M1E6)V$g;f5%#PkyvK>WPEO zHTvvTMDQ!`NJUUA4Fae)?dqV(&Ia9wtREyq>q#dx`j2bbAiB)6)z-j?LqE0|1msVc zBe(D|R(rw;g05&M%9-!|(>6n8sG8VT0zG!!Su%M!{ReI5?IHIz;l)SSn9suV=hHa< zD2)K&POY;}ov)e}Pvi|_ZL+I;cRnsNhBajhfghc1WV`_afY=Iv1W{Nv0_MwGiOyYOKdC{ExFXz-Z;H zNG}1$bU16C;%0{9reFb-(1{2E&-;nQ8b>Bj@ITl9XaP$635<-RHg7{%p+K`M{nub= z9h3uNPkmpecrHg}Uc~_mHhRiLdH-1DS&ayFF0!&NA0zQog|T0~p=SICvJod=uJJlN z5j+RpnmQh)B)0Z}2-yCs-#k+2#@Qiw2cm!d>Ey;kRk`o+*;l8o|QN zlD;N)rmv8$qiSxUENG1z^e@S*bOE3Dryrp=?Pb42LfRKgKVAXuntRi-5?Hxj1{+b! z))oc981rioG?E($5Cf8;n48zw{*1|(kn?ufRG0NS0BCW6o;C4=u7DFFUbW8QhVLI8 zeBG0(f-|lFqoxfD)$57yTAa)~nW|2h;a3AhunqT(c|WeY^>iHob_rsYAyWw?G{1&< zCm){sgk=!Pupi7Jut-^W{BSaEEjw%q%gt&8^G&Cit9nQrEGsU_~a&*LepR%5@DJsto;1@RJ8OE`?rf}Sx5 zm;DFG|KE;H%@9lS*cg|v&vmpJ$J64#`D+Zx|E|6q5qqEdp?+vk5^4lrqKyf zpWn;7$&7n3UhG=XW7|Bl`a;Opo_|M|n=?+JyD$g%k8Y^5O9uX9=T48Z-PD@NIj$Cd?Mi38 zJMkCU+>A|p(eJo|8coG3bz-L(foF& zC(J?~DB>xF#U}y-e|Eky;l!OZHl%-?*&?XF4#Z zoHS$gtdgDIGf^vdy2KhC<=@|nb2CVzrSBW1lrv7TU&*VuZ74%U!>xbBbgRo5tQZ69 zLTg}R0#xrXG8jNpd!RQ~`n8yWrmbd~&X@VD(1O5#X(TL3ba)GY1ajcHTIa)4sSLv0 zojb6yJYJuoY69ak4&XBOV{N&L{BypWNx8P9jsPs8%HinBv0)8o%>M&SOgL(gluYt( zmDh@n+QJwtPSGxXcJ91)m5mWxAT5zvq^R)tR|TRfOdiuX+FX(1#D{WUvLHQ9YCsmXqQeu|!dLo$^f z9$%-?w6RBVih%DgHNb^5oFW0N+-9@hu(+3Fr*h@`Ept()PY` z1FTs)$GQiIixox;6CjOVIEdCUhoE+C*f~ZfRXuZpz2UQ70#bDF5ce4fuSbGS>$D^k zvsl}SQ~&&XfH})SSj9qa!%B}tS=0VVRqJb-nl7SB9?2BnD_DrBl4^jc~12TCShZ_dHM{X=Y=_ehXODoOxe%ywIprrNb)__?+hd#HO#O&Y z^%^oaGJy2#fQTy40ADCzs08HPG3|wrc!mQo5^Xnq#jI||uO8amW2?7iSF&zV5^aZK zum-)nVVJuJu|yo`hW;6V)2Q=@Q@AAQ@Ptt1P)-oO*EMWJ>R6bS=PT3r37#Sf7(ci3#;f`}%T0+GI) z3EgCvY|&}tD^`%i`~}4{K^TD>u04U!S%#?)`q5hn$X3J|NH9h?j!9leTL_E$8ctpM zM~h#w(_%;6GBkV{VurxQ(lD(1_leMbq!M$T}9Wcg07{YL!=(~A+=m2M8&|08#dMJ7}UYmX}#{3|@X)O`BbUf5a z#cHZ>0~kGPo*{8F5VDz{jDHN@HFb-?@eD#c$p$Cl137;&rusUIv;;gr)a|@up?4P8 zvQ$UyA&ejS+&Cj|Vm`5c;HuUyL8tTd%6Kcr@OTYE38hCbs?F=QcJJ)S^;Jt`B$S($uAm~ZFk*v94L4f!_w@CWAZOo8j`ZKHii)u(P zJHTrmrg-b>{EW;ih=Z=BwutCMCcbKxhFwuJy^86=UpMSgLbgXo7*|#2^Y;rJLWI7} z$=;PjmY`H<*}OCuem=ZJcw=ooAHi4t-H8GOtM#aK%7<&7dOx6Eh&2x+%1;A*Dg!5-qdwE;b1v81*!?dETvMe!J- zEGsJ*sGXgUP`aRV32V$_(;>#TcOMTU@s+lZ7aNgC5&DOLh<&fx%r+~CP>Dsy%ZC}B z$LmC7JVU!6H`X6B<^pdE)x#CfRCy#ZUCqgG?%Ql6Nb9r?>_M zB)atKuwD3;&|RlYspcI(qKdm+>m^EE8akJqR6Dhs1cB@1yT}dt74{jh7aVO7rR9N;X!d|NP4W z1HUU5l5eD9PR9qAn9lD55AFx!_Y|xPi_`a(U;r9PXbgL{5W2=XYixsg&RuCz`hH9x z8`f{l(0_=K-MH?OTcsN)%B*xfZDV=J&aJM{L59yv{)luhp*#rVtTk90SFiDO%J`yL z#F0X@{AyL{a1t=jwKlV_z~^;a0~s0>XK(ir2ByW+>rBLKk|Qa6va z*NJ$-&t`Z1TRL+kmbK!?s8~~QVX@*+?Jc<3iX#e~StA;p^=(YEo zOB6#8KhUxPb!gI`8|`JU6W^`(z3$yzRJrNYVu!hO?Sa#qM`@zXORDg=t zHS}Ga2msO9=jm^|gRUM>=mX&fyla5Ku(n~3oof6BoZ>l2{sO1I2i?S%dJv_pc{qqT zaYf($O~1!Jl`pf?FdlSrh^o|reruh);0d>(?;b$1%6UpFNXtF4at|f=z=;WYEkBCQ ztOw<#YH$_}RwrfA{8{GnL=tZ4kkhdUdilfxzZv%)%60y3SjxnuJGj-?(f;V$6MA(A zkO8_9@s{8s7L}b>4fl(WeuTdXTrnJ>`I^ZjPNLAwGzZ%w^&)(dOXcTr;>$Yf?%b3% zLr?9#i%Gu{Lm}A9+=svSxD{)x z6-uL((F`3dsf(-6Dg-S%YnL%X6WwLwK3SkU|9h@dNZo==DYjGgL_W*ElONvD87VdQ+W>SH&2g( zDQ|`p=(ikC{IBaGg2%#)w5i=nH|!uXtfSA$V5oV%TV^QYXT{o8{x5YOEov{=Xdo*V_Y(cA2C+~HZlP*H{-k<@rR25?lkt{p!AWY%u4 zeJhkwKtrQAlely?5s-#PTmVv5rYK~ zmMokQE36+IDxz8@d3P5*7zdg8p4`9Ue zn`rEwlbKpYWjh@U11(N^wCLLPB}RnvCAcFmgGRRjG0%WOqd?>nX;l!}jO&!@3kleL zIIvZC}v9*t$(D;9mj5>fn_r|%W8|nve%JkTKo3i{r4nv$@xh^=!{ zQpIcQ*dLN-adI+wZ(8H3*zW6^qU6Bgd|WLa!?X+mQI@- znnSIKw>fe#XTol9QNP(mQ$qn|a(vUkY1R2B9Qn8Ha`ul19`B+*Y9X7(W<~?`XIL(I zUHVx}xD}p)I@kTm?w{P1_m~Hy#Q~6)JsJZK)nE*KBNZ>sRpyZ(-2ZZ?zfYL-e1>6A z`=;Di!H`jgpxF-h#7e-f%o|ht0>_iLMOIODVSW%Qha)NDiHO2~)WRB@`QD#NTKJZQ z&=Ip$nxbJAHQe>To?WJQ{8oHVa_-FJ5dJZo6tNwO0Zn?xqw$)F4f8GY%zs69wUe8< zL3Bhl)yanZ7wV!$0|JS6Zta|0JpGKWGy!nHppSr)JC?zl_QHXrjpzm-s{&4Aqeg`W z##Z1w%_J0)FIffT88&l-Ee}XhgE9HxWM6R7>6geTabjNx1oc*o?@yEB%()vEDrCWR zpvyE)Ifq{NvudIMhJ5G5b={TDANx{4Dw)5@XGY|*|i}HLw$}#yKL4Jyd)Dp$$xG9fyV22=uB~G zjCFfY(D6wL8|yQY!ksnj7tWCBe<)8sN z1{RUTGJE(pR+NTSS-QOku8v}^M`Z#`gt`}UuG(sBC&%GXc@C((x;N`@q`=RsQNqG8(Vo_`jTH*N&h}vHVjNzYYtGr7y_00 z*5Me@XEYyHR|oEr$z<@z30`t8tP5&G$4>=542|GVj<-h9*)Yq|O}JK3=AL{f@ajnA z99n5%W$@kCgJnU*Gm7-Tkxcu=Y^|0|I2QbTrmYi5} z|6iz=1`RIq-i58|ONROWMRY?4`+utd%`AJJc>7mIEDE3sEcxl-u#AELu@!#V=oDTG z@mb-`F$w1dCA&JieEHcmSYz}ZqAl3;e|-($mJ&is$`F@|-A|~)3<9XS>N(CAzBc+H z@JMA4Y#0(bU{NF)`hJ@D%dqn>{BiXBb!mMj63F~`tEBhTs87v)8n+(RJ=KGT`tZ*+ z^C=~DiNUkh6K=_O0ZQ9{H`E3A$B$AsfTAn*s*;ek>xk%?&wb% z%COPV`K@C$+SQl?tu_k1#=Fvu28Q_)9CZXH23-Fo9Im35_Vjn3Yk8w!ML~_PbQ}Bu zp3~lKMxfXN5d!j%(~$aKp06PBJKQ&P8|rqciS?7(Saw_W$Adcy7H?Sof-?y;8@brm z^-E;#L*yV_7e;?9C?$Bq*$3_+IF9mUrfUst1~_F+%=SxlSbv6UAEML}_KhRdyB|gf z8kZBEYS|f#upL%u`u@ZrwiRtE=q;;U#O$y(=CEXi(rj_M-YXSpimuy`rdzv6a1KGjG zfzfvfOcPMJ(1Fe$DTS$*{9c@1NItS+#V4&xw@h`}#^^|mas%VYxFf0>^6Dx88Smsl z`JTIZ+28Ch_6xnXkTs}2)^hFW2wyRzK)vupl&X>28}6{w1DZeF;6vfGWA(mDAkx`0 z$jR9UzOXIOn_NO`$4?;&i}?KRr=uMz)y>~jNvKbEPsm*+m~9Kk-GLu!N~i22Fecw%TrROJe80-3)s*Jx7cL-TSF({C);C{vr;6;X?MEsq~u-befQo zG3ao&wcBaxeCGj)-z1s$5fj@duMArOwY6jXqXYc&DbP%NAt?R@EYt zjIytRM{OQ@ALZ$1DQ3c&9awEJ@;KsNoSEhcq%{~L zD8fJj0(U8Dg+&DY?linrmiGd(;XU_>#HJ^4EReiQUyPo5y(X=BC}v`$zEFAA@a*3$ zB_V**+5Xyjn+>*)oCMRRyjmDO?gU8@y^@fzYAOb6c1~VHpOf@%jAX5Q60Cm?;v;^v z!rRFy^UT(du&L)uJL1@Ve+$RVVN%&2f{g17VY|k5n`T(8mxL|q0zs}WNw{ZccAjXM zQIB;>V6l-3d=xqQPVcMQb?t}+?$&w@;QfXUWd?}4_jBjAaaBDISAtNi>#X{>DWYA!7XvSN%zD3!}ODj82K_OlA0Kvi59+DQ<|$UEU`Ar)RjI3RTpK zYro$(ogQ|Zkw_7(X{N<1?=rdM*)x^Fp<;j(B`|@^l4=96__1~j6|M^knd=i}#cfPQ zgp5;?4xWhk7v!kvfB38+S`2A^UylnZ<@?$5Fo8wcJ-!Oz=Qm%x@>VrJKN8%-f>#a4 zaIpoNjQEYO{}4oXF80kl`|;y*-YxdEH?;SG@!ioIruQp(D$vU6nd!R)Yx7j)?@+de z=*+CSdxj*UYMUl&|6{gfkTubbqb&5*n+&Ds_l2`{$F}uk#}Hkl8pt#w!MAV%d6MEW zbtJXy7=lTCH~UHXg@!~!x!1-gWa7K+8q(^O8S+Fa5ZTc|oHHl8_f9rXh;nQFNy)MK zA<5u|%l$EgK6LqEMb=R^!t$V0T-f88Xmz1D!w_U#V&cVO89T|t`%QTpt%-Bg>~=9R z727Ub;F-11+)8;DN}nc(TXvuY86bNSTzBK|$5V*LG`%w705v3UTS4-tE29lE`W*vb z2y2aJxGv%Y`s56UDZi`@nYfl729dL=*t$+ik#DXZISUDGnr`y*!-S6^Vn3<4+7sX| zFrgdKG^meD;M!C|v6DPxy7$$fa5HvwPJj+Rw4N9IEvg80ia%6%X94A4zjwF^OOImf zYN$ZM9b}NB8^Na%%Ye9Nl(>X~|I;2pf4CejOt=0wNs)wkQM5Y*tb)m`g)kwqom!$>s141a+W| z@~z-|WQq*;e8ymZq4D;e7QV072+6!qbWAd}c3gFIHm$mdna~_NJ1X%9{fi;9e z&h}m85FyhP(7&|53oY_Zt}*m0i+=b8)#k2I=BA;6ix1C7c};S>-pVr-IzylfBgeh1 zirubr73l)E(arIhC*VE)8I1%=qvm4*39c-zl6@fBW_hbrs9CJq8fPr>qbz^K2`bSv zN#RCL!^Ydu^x*w=oZ}cFEN&H#4#8ZDH#PhN;K@2PjQi`>E&?G+x&kxJS^#%mMnan2pS+reZHDL$x94ggzitI6^%W}cci(YFAY!IepLtR+Qco@% z0KtUq=>BKBdTL=MFHeJ2xXaQ$c#w^r?aqCbP1YKwpu6CvKHk_gROm{f8_s9-n{Cs> zI+EA=J@m^(Hx>UoTIIbDmS*q1<+_mK)43}%@k$VbE7>S-@sA6(t+^+=DYv>#2qdM? z!`HQ$o0h7<7=)J^4hWB1QbDfeZW3XLRL8w~Yl-v4m2X(EH+;CJ3@da&PmKa29ZHAc zSFRP_YuB|Gy5)!F9bL;pYw$g>V+An3wubqlgzydZ=2z-kQHXKYm`@%Dn_wT?9-f>G zVA@6}LZ42@WH;^gOw9+$8f@NKTc^Y**%~ zpZ+QYD`zm-3lu`!kLO$9bG&i%XX+r)_>T+`ofP}=e{FJ*{M=WfpmoN&WBGjLhI6{j z8D%WuH%Qcz(c+bNC_z2&$@}LSfR5$RBFZUcZk6I2jDMa2luw+Wp7kDC+@LP4LSk~q zy&w}N)qRfqJ}{Fv>0Fa8bUW0nz<`8AULDb{jt?09h^Z4H{eg>5FLO%`u}{=v3_3;y z{e+38$Rb=Rri9%{B4EWOj1_Sk4zs>Za)AR?+J^+4na$3z-ge%haDD<;FK`1eb*Gy} zn%9x*4JyohefQqQ4|UHm|C_IK%nAtag^S^+=ZK`wJzZFa18!Pr9|f#)!I*6>_5XBA zT;i+4I4$?eGqztp)APFeHbOC2PX@rM?Yv&&d|M37y5K!pAP}O8p)5)oGM4_vP!S4` zcFfwq;?mY*@`xeWosa)Hqij?K&s1X=ez{PNs3=r2_hpz|h%V@APTLrdhS*V^jxjHB zoAYR!u|H;y1AG)|0HCS=l&rmgnkR^OI7V$@B|vFTby*!bENp}Mq<8hX&C23?8QZJP zzyyQi1%Fa<=dNn(A+%3*goGgR6W_JeBlp#9LA=ao2{o9S7V@@|sf`M*&cA{Kik43g zz6txamVl$!Jr@6qE@4HWHxkot!V=H!DFa8|m~G!yxvG8S;2d)PD%eqd8QZB(_ZaMX zWDi8Xnpv*F6_b`FLW&~ksnswFm{d!v{#5j8;P)YKKHqDOvlMSaAc&Fkn2kG z2EG4pz%!0DW-U3pnaAO1`B!mDsR->uwDO*g^798yP>b& zh;peK{`qEco669&2$03&QOuo@{W1H>v3P(C=G<|jx&2F|cXcx3??y=5RklKio*6}x z)0Fcg1b>MsrTv*Nh=O=PpwZtZ-Xhfpmp+)E|9y&iP_bfPGC}6~OCu zK(W)!%`n+np?BF(qimktOXcx&RNjHBUsg$lNU>k3?pf~X6AL6cD#^H6o4&%7`v{ew zzUtPJ*^eu>JC>l*!nW_p{xHG$|CS5^-rtkr?RaWgGH348UWU0r`jks(w`GR4xfu=yFn7oyk-`3WWqa|Asu4P zudUa_ok%%|mIB#f9w(dy+$wzobe=i+c;@P8ed~xY?*({k|ts zv0K}n!z6p-=VGH5~aHfwO&XD4FKM%x%51@%B%083D zEiXRDD(o=sqXFaM&yLu=!s=NaCBXn2>TuB*(R~t&Z)Gi2SX0#z`Uz<~?q{mK1nW__XhL)PjnrCxL>OEz9(tmtodiDlG=YSw!pH z!bGu-MX{1rlp50B9`*uHrb@}TbwDEqp!4XonlL>47p9~T%g?nxY zNMJMNd8DQAw#qXVqAzV)=d6_a)CkF?#8JtQ8+}Au)_iLuJ{MuJVwhJrzUr0Cw+in-%GrvCm!cO4=W)LAgGqy{u zrSS`%mk)XSxkcZodQIwDWh>o~0jZH`=`s8YqCPcECb4ZS~ z)>~u42WL=a?02-&WcbO7>34Pr5_O$n3u0{qx1T8o2gA~(1z=CgN~^RH$mR%>tLLP7 z!n}K?S&F{_0qEf@BwG1>aRsN7pMN6_MHiwuMq?sw(JrsFDYKatdWO@)IiL?9YhJA* z6Wtm~>dzKHM7)`}XI)JT!=q|@#bkE}C2#b^E6b$snWs$j+XRm`PhW+-O32?m3$_=M zM|1$4tdht*0!H#%%1Qmq(j268a#WsU^G_As3w26g3pd7~<&i^bl_g@Ei~sK&NYr|g z-QiojkJsB`rGa+#I0;%Tn#pca%b*@^F*476dQyo~I&f3Bl7<|tJXr|q{r3il&OiHw z>h>GA^QnH=%T}X~w~jf2&I1xyrX?Hu`ZK#a@Y##2dH#6p2#?~Ak#C$ZT~u2AA|sB7 zMMuFX4FvtlG?RbxUtCivTQ*(o7>mvxQLcLal($3r3DRl&SEmJmc$ZI!p($Hpsbi2& zS}Snr9W@G)bNMjA1GtAmRl3-9W9Pd1`MmOSiBj- zCPQmEbBRxVt%9h!;q zh*Jd9rdV3-XT5Mb4u5Y#rL(05p8m~Zdo{!QOQ6^|c-^1<1j2B!J=OXjQ!!E6LoNc; z$d(CjYTrv6CXEBm)_q1o%La4mBiEdk8=y{d^}_}=bkyYE_v(ZBjK_qjXu2dw-GB8t z&!os&%=~JVAaJS1lSclL8{Hd`t;T!K4nu4r9Bc5xM+`tKZCAhJh_-C%G+v=~Cu(b< z-q5a#fkTKvvN!22INN`{kUx89w zMdRH=aOrX%eN23jPWBobIcL#B)oqr>-<%hJs%&G<{QbJ5s&qBbYfs^aNq!kIrZ3ob zu_Sj42ESnFq?Uv<5&C!&$~y;|)atIC)9(CsD^t5LIkj6uwFLy z9XX8OW6rNR!vhDuh^w3crCmCZxQ39XuGlXrjQ5#Tee?tNgsYl2#i%Il(pSe)g{dys$!bDh zO`ouIX*G>6OHU>FtO9-~ELVUz{KVAnUw6AxN?&r|dqYeK<*p4BR(YO!ToI(XHkVod zM;FrGKaS%qg^XW=JOA|yf~6nv9~;P|Ktx{6t3#+TO5ftsp=E)*qUNT>#?#n=6idE$ zrqDr@nJdrgOGr-;dPmJ!VSXJ;LzsYe;JSS?fC3@H@J@-cDXps!l~NgL z@{SPe`-UdqElwyL*cfZkp$RqV4M3W+%wfE|7s3_mHe*4UMV?vAzHK{4YkBMhM5*d} zmF@V_x}Ro~DTk(+jABd};9)$a_%!P@rDeAqAB^%5un75m+FL651(SspM$dWd>Co7? z#v4;W;ZD@D=yg(4UX9=a*LiH^ak-VpZmm1^YI8JLb#g36g;BHWx5rFelFfx6(Mih@ zh*SlYp(a;u19Oa^WE54<4wKOf%CC~d%8i2x^-|>Pt-MgN8d@(HX_}MYg-yp9594}) zj3th-{Lcn|pUa~e(Mu;O7f~Rsh>@F!1i|8@(ublfS&O&50k*kD3cKDzW=Yw&dNmwU z=dAHS$A5}j@$PD4**|kXS)aBhcgWgyAO)Mw20;VLCqnOwN*^h8(_bwG>xZxEkm3pH z*C{jzAS@#T1|A9JdpAE2Cs0p8T$3HRwY%HmJ!)>F@RZn*=<5#?=dASbpU`z~KhwDqPyM9WF<-xcrRUye`((l74b=C?4;BPl` zHctZ1MD22}78FJhOGu5|M=2cL{@FgsVX4Wat>Z#+F3FFAdz%I^3B|8tBJ^U9UZAQe zHm&B2H$Dkf$(}ZC>eMg9ToYU64Ae{0YmA?^>tYyE^3J+`s?EcrJE{I_A{2>H(6wKn z?kUq9<0P;5c1+M~`v%lrL#RsH}NXjALWiY*e>@m04p?aqj$92F4&n29e+Awo7epL=7+PE&a|f5np-Sgk{0+(c$In4f5kue)%?e++AzMJ4k=#e_ zIulQt;{Eo}-H=>rHLpo0&2>kE=bI9POHn?OuxD6EwRC97#IrG$)_9uu`9Jh&9)CWc zl?UU%46zp5JZ~7B>n;H^J*TN8P^5Wjn>?IcEW<%4GIz0(>)^ZQo2*VUvVz4&>e5=O zUq)w%`KZDHe|8pIdKLlWd0^64y@0`rFoa=U%91; zx|M94-20U%u|}xfMrsnsNfdz^Q3@P-$!sJYbK-;YJb?NAe2wFBS!+Q?W)lgl#C?cz z4H)%mj~>$dx%(61#b3L2gL&_eJd_M%IthQNip$uJk9NTiG$yDUw|H^P6U`a zVW1-CjIcJ)%V{jNYAj{^EhtKDkB0+TiDWZnDX3vl5vpb^9Uma_%HE3?7>|Qqic@#< zS~XxgopCsARP)U%QsN(NeX%tB(wJexC9P5=_g855;1SJ0czC;0jO(3bwlQsGdgEE#v`@GP3y{Zu*_-K-l?v9XZ zQ(ShxP7P-wB`!tSux)IvwrsQE`h;2}n8w@XINxJPL@=$Qn=Y4+mA8e`B00!0=|)de zpK&(ntO$7J>v%#);i7bMtY05omZ)0kdlRavpDDo$<}R3nNC7G;GrBiC5U#9=6|Hm{ z>iAr2D7_pmd$ahSw7v)L$6ZDyqkbdDlL{WlEe`PObcnzWCse|Z46qPy2E^=CYxFjxbOybwh7$iROS>6Q6i z&~2{pfZmmlF+*d!n= zJJ-w^tClRmJrihVxQ<8Dm=r0T)=i(XhsXSXz#MzhdYdgch1IonUQ2R!rk2^}(3F1< zL4-YGv@Y(@8BkWS@{dita~PB3nY`%VLIJevUa-6|5V=6Rd=!3{NJVRU~VO%a~UAfJ+m6 zE!aPJE*{LT=CF&e< zk({fn^-jE**d(0g*U^webfH@|=Q@=U1wKPjVVT;8ibD6u)$0zn8J$|-66FE=<%yDO z!XVN}kIzLtADwxx?-uFke;y_Wx5p42#Ha%FPWyk2rXAE9=T^CUTrXj@|vQ9AUNAAZD)w!b7+tVNGYq!q70TqW(&^n zZN*~zU+3P+T_P91JuN-T5JQ}zzHuMd!XDqrj72+@PWgvYhmfT>4K~{Rs%7%Eu*w97 z^!N&-YM2}LX5*Zy+ks{<%RGy{9PTZ9gH(kspA|4-nl=WHE|3m|gyNbV^mt<_KY^e> zp{uf~qXIV{&U_=mEwj1`%Wy^-AE+v-=R)ngL+Z-zT{(JiTJ*@H6Huq)eT1dB`(;~G zqP2zRKfi6pK{Bh@(nvY;yt~0hfhp++^U_>L0l=b)R0tp{)j~q2a`$2jT_6ztj;0M> z)i&Jk9oI=?DyNj*W0{4Qx(ly^JulyOH9t&GP3+ESE+~!aW(_JpS{magULH49@En|q zA$Is-J)YN$X_uLtJPR5L{59-k9yBaCM5aTBSO?tXeb;*{cHupJr+F|IOl)*FH>VRO zwxAoTG0PbV`v?5ms2*U|gb{~0kB79M%92Zmb&DQ4vYV7Xwh!v6ee9FYmdSckvxags z1a+>zictn<(()woH%vcxzNdACjeC3zQQQK2$54VcDGx4g;d-RkRhI!dLd6->fHM4S znbNlQqNuj|ks@}`!zc@!%`^Lc}P{0S!@XAIS@i> zU{{|`6-kx_Adidd9Ng2W*92P~sIhz&@omL6)Vc{tbk%#1mW)Az|A8 za1IoL*m#2mFV0-4vVGj^=)+iG#J+i`@=LgP0edfoHR|%J8c(F@btk`Fpcd8 zNrIOk6%Xn}x6!F-1C_BEqHLklTIk`>>a}8l`pU~VJ0_*l(1_|pEurr zcU3x_CxnG3gh75d8JnwyE9z!o_n5~V?aK|#?mH8c#_{bim-Py@n58Uj>Ea2%13X@gRM1wd7%*8&^t{tbe@8iz1=PBNqrkp4@_oPxi0B2R8DV>;36B!)n{h@V5~kSz>``c-r)RN*u>0+ z$U~n1Qv17UKNk?!kNF&SomOh4=3N(Ac}Fz?N>)64K<&9&?&)2!Ox{h0%9X?u{EHcn zz+c%0F78N~Lj92xS%&9H8D=xcgepmfP}3M<38sb0c!wW&e#;{$Zu7SqHz4k80e?B} znf^l4tlLS8_`sNb8#n~?&~h1A&a@{}y#)26wr5rnV8uxN7RGr<;PqRIV?S6q%+jh30*&0jWy( zmt%&d4c*&e@bc}pRS1q-(eVaa_2T#5u2>2v-Ps#Hw@eh4$TMupcJe}Ri6QDOt|#yE zJA7(8GZ(vGcVPBoZ5(S2!rEU_M4KQ&?W!H}$R~O-t(I7J>C7yPZc7=rpOtson**SXU$mtVPh z|Ej8@qn@two9!lj>X}J3v1NW9F@cu<6q5RSJp5-98DE|AfP=}sx$HISY)%&jOJ#t& zf^Nt?c112uGPG5K>|RHS^)ZH+B~z=x$Eu-|Rr{G-jaCaBV1Q76tyn5E)E!FL|Lvzq zoX|X!JRKaxG$9nBBB^L9*Q+=ut-NuaV8qnd&TPCsQe;?T(sRzvpknQrM$WT6N?9C# zYM`bqgVT5C^K`LddCV7`Ujx?N2RxS2I}!~^^^2?+BI+GPio#{tZ{J~A4gvt%S$LwZ zJo}X4bwXE1I zF5wYD`@*jocnxHL4OK2FshPW&`(GeSSjhq@oad{wl_#Q*pri0f0EDk3gYU5STsOwm z`)hMUXWfufrbU#X>$!+LmoOH6FS@TRx_Xq2zBSe-R%E0g1-H`MRL-rNOm^|#z<#Y# zTFnx{-eE4x<_U$D-c5{vX^Ut>t;&fhKY>m+Tm+W_W5`kG=x+EOWqzJFSN+ARe{F&8TmI&Fue+|LUQ7vjHA*faPbOshp4nd%?cFr0jXO`T_ z1AGgCq?d56h#)3($99hJTN2Hysu&7h;3q>@$E^;GO^^gw#_t&L)k9K>sGjWYrr+_| zjj4Dax=$x-+XpXGKf+$i;4Jx1@2CN`U?hRKyMDZLFUh8q23~WRPq46*nw-66oV~D# zCVAgU`;!m$K9KGN?G47G-Q3Oo^_gVUz#Yh*^dncX+KUfW((G%7mL!y0s#Vow`BfQM zdmQdpT|+f18r+l*EH>A#il4*rsNGP66G zm^xv$B!3B8<$?r5x_}UNKiaZxI8dK>2T1kOcL<5)%Bg zN$YrjhM7cMtq-X;Hk5RK*qLnX$mGBQuEz(bM@9<-C7dhYU0GP+#C_Ro?+=1N5bGv` z*ojvEK>ytXM!k{|#ySVN$-qQ`S;~M9XfhjP6#EX#@%UuEwv|XNo_7zd8GNcq_XHNs zztj&d>%bqj#Gr$H$|#uQ_5+^KR68Hs?FDiWoH~b9a+WHyL&e|qN zCw;{)l1I}fCp_h=c+%}5Hcvg>==i#ky0Mt-@n-QdzTFY3A3C4Q5JCOnc7#6k)JKv* z7*Jm;Gi%heA$0-Cp`Waua}JYMw7zkbmJbp7T}?Cx3Y!>JfnJ<7$G9*^O1}R|3WQlU zoea(;`xTIr;(L;bX1hUrx(0(?RGe^U6D9sp!d#SL1ERJQ6k2=ro7ZvLKKx_+3Yz?D zY@VZVRo0$AY^o+?=$G^7H5+QQjt$pYDqSnMwR%gkNryJwy&l`F3$>Oh_i+m>F8pV9 zbIpivU8TVF>);c(Ir($-o`JM}LN?9M&8@}4@z1_uX4RVlu&4%hp80MG#Sv0~j! z09XoE`*fOV>IPw!21Xb&2xxA70aw`q+ty1OG**?=_1U9xUIuPjQ?1}Qie zw<&|s?}y6;VpuisL8cU73t&l5KJ@PVO_Q!hH$w!39yp^`myp%bmP>$D*!3B!B+}nt zk*Yc{##!VrYi#e!la@0gPD?Mla*80a^n`l7k4@dtSEp=ywbX&TpsOW{z$OeBG?&@R zyd$}-U->+Qr-9|0L*h^5KMyB&+X^WQ3SWPM!>eCo9weL;5aLJVcXZXvgA$G4aJQN8 zbH2_EEiCdJbHCoWL~?MI#I`PWx~( z)_=)Gaf9P~0L%HBZ0H43>{!VeS{}+aYEkb`##O_nC()2rx(qCxMZ15ppT?Ha#SU^+9OM|J zl;~DB03T~|rjCd$E3r6eU@EapT||T-J1MoXSn{xFVx#rRTWHoX~m$o=*?WwKU#CK1P>q(hA+R@XVuSuxs)Y?Pd?sbq`Gf zao9s=8{#SYk;B@l|;9bO(QW^p^M=Hi!TC=QfZAf70#j$#B9MG%%LauCYVRYozNir5Ka>}tOf zAh^he$>XkPP@kY*WUvZ0+CKo2eWGP*tw@PS8b(-1YF&RCU#srK7EAVw|4u<^^< z$A*)njEA8a@&P66gAZtdaVUtd4fXsgTeL|YT@j$SQSr^8|sk3VJB9Z~Dzk~H} zNGCp94AVS~DLFF=GNoFSxGBsJk-Pa+tSHmt$0c8C(~BLf3;tcJ>mOH56AdhCWF7n} zF&DHx);>x@O^@A%Dqz-9s~BbV~G#BwNK~`uR*77x-t~hV}K7 zv7NzXIu?S(`6wqK{U3pQ4Qgf<%mP#p%*(d0zIb$H&ZfFhnBMV)U{5pJ0;LFJnpb!4 z;~gX8fFQX-Ofyx33W~5G8ebxx&ll+jU8Cv83W~FdIxRLp0dK|Zm7*7Pw|uGud|fGz zz8r9~hPKe2Wr3Br8(dG<7FQuJacVsl9(fq$4mEj2laX*hReTNvp*__knP&^79t`sA zy&vBQL+mQsbuKsvez@Pm9^gk_X;d{A6=p>?$-T)wpyG6Clqt zP?sX@_&FgDB%L2D56{9(s+}oV2k9SVGN*eFGoYn|cb=W#C%DfrPjQg2^acKb*H>Pd zg+R?#bm~Y3>D(-C+}Nw(aY7EilFB$8v0sw=E$MZOFsf=O*GGxS$E?wejdrnS{y-mg z^W&8X(Rp#f6)nNmi6%*W{!dV0mi<~QH7#2#Ce>tw)SMagejJ<0$Xb1q0LWDbsIvt3 zT^X<@+gzPJJF4qz2&&%nLQhPHg33aD~oe_g$R|mdRZ3q^A$`zf8ldN+aXzh4=5IHK$uk$S` zcD7_>@(QSMY@pPl29FZmDaN};GTmh`Y4Cjyh=s7BHpUYJe`XPZWk&c7l3e%JS*-zI zw-3%VGBdu_)$~Y1oMV}enS(c;b3;jXiUd#`&*-=dYJ5i?DIm1}UJ1-+f* zXFZGjojrUx{h}2M6 zZdsN|h>%XM)=BkB1G9z&yx5DP=(yO=Wx(e(=8!o-Iy5>94I(4!;ywp)4h#~x+U_4i zgXxKrqJJc~jM=A}M;>4mWgrlcu!kmkI}|y*2${u^WIx8H=<9%h9lcwt3t+AlmG=g4 z@NaQlnKiCuRJqRX4Kq5ToItp)Ikr+^FOdsiIhZSW;xo+d$qfD<6Y zAPahYUUF7=N0knAXlpiJm!RcX6B1^4KDrHrwr^JF-@78lVS&sG>&Jix)O2Z*#5OFl zi4W$~$Y)8UdfQnFURyATpde&%qiCCzr_Ut!Z&WLfB|!9vxWI{8@rnM}iEC)~(a{4m zA?EYXI73qGHAA=tSeDPv8rZZxBP?fH<~NIcd9Y4_r&oV!vgys>PKJmz*2NW%6!=Oe zR~y))Pg$)H$7THgr2J9!eY=Xmw*I-;_^cSMbA@b8_R^d{O-HdpJ61b!VrPbOIvD6#Aq9&kJ6PhCwB?h9t+qVHVawQBv~kB@=-s zqWQpo#)7hD_kg1lR~w!$6F|sv(nx zqeJCNGyV-EVq&RPM1Z*)f3~u_v)&9F9q2V6TJ$Z9^Uq6hxGXoN#qNn0V``*&C)Lx+ zyks$wB2MvPos7(?R4|HW5rGi_*V)9ZuFrnl%K4F6xQph0o5L?qz&@j=fIY-VkhAmj|K#g!BXHX;aYet*!JTPXUW$p1#VL|-X6cA+$tMEK16(cic&}v7R z7dt3w8|KAhhw%7bqyhiU7yGFn%hW~@-yleeo2H*49{n|mxM=%HxC@>Ra_2fU`%g>s zfu#WNsEd$#(t-Yue0LyNLkzOjwbyvm`@=A3OZ4B=pX_I5p8IMh{g0Kqm#5;#qNkvc z*#y_;4b_(Q6$(LflJYj_L(3a4u|Hc>TYEM>jBmACia^jzQM2lm-y|OoKg)B-dra#V zXv+=Zem#(AE))$X-x4Zmc6$bC>2^TDcL)m}f$@wOO{ca^^35>86Uef&UBk7(1WuL9I<@p1=qO58x>uO$K4ZH-F!X zBz!kQ$m#}w+x_vbUt@n0fB_+FzB0*~|7==NF719A5viKMm(9fXRf41b*H_^vXVh5l zE2Ss7-PPJn4a1jld#tf=ESQem!5_ogypngw~`-scPj+>a1uJ!qU#3Og3eQI zAW;gLkkNGjBI~l;&Ckmc!Uh(OBoH|Z?=ey7DiM4JPr{@qpU;r3sVuPLeXo0;VTgh( zsQJ_vj~cb8x^e*u#*Y;ri>jMqt_;Do! z4y+-@q3&mz0~^IQ&{>QSVoLBioS?YdBMoyw&8i;b3evo(kE~HJsTJe!MadGb5`pQd zaDeoJs)HT&J6Sf)an&cPC2c+Dkvo*KauhDzC_J7fjx(YC@1k1=DycxTbKLaj{UnKTxDu-|?Yd+dYyVqa zx1vNCdqihwO9HYTB+So{pnR}_?M!W*SmK?~nnr<=%kQ8?cv0y0{BrE_FG{~nf zKai)Q)Ah?+yNkA2hDhz+0RG$SiBDV!0iBz5sX5s~-)KBaKx6wm;mfZ*fz%iGS?_oo zqVGZVc?dJENIHIdSN3FtRGZpw81jdi(t38{jGZYTPpWXjVPWkPlI#LKXeKlx({AOF zJ087foci;S{gc8bjmywnCf)meSIpAAs*)&IzgHre>JaY%wqZ}j>9PDABGnANnQK`T zaDEm5O#p@gI@$^?Y}_^*Xd@Dl{GvjYfi&>H4+LK0pE3X&%!QYhg9#O%-`Mv)%f^tZ#nMS?_aEcwN)c>?ONW1-oF3-m!0|9p}!P7)9$*lxF0+ zR&EomNu@1!yVsOBWY5wjaj@1xob3Tc0n8p;EW)Wfia(zaiaI;0vhzrFLboQU9zE4j|if}k2M_!;OK#^A5w?^d+HlVXAw#tyl0Xyjb~O{>N4M~OZ2-;^wR7h=vesj`XnvcAmN(8d?;ZZ9CR&0e2_Rt+8Qui2X4XFQto@U zR#%`(+&8|RHHP80SoM`W1D>eUkCqQ$?E~o5R`>X?ruC+`tQ{H|N@u`A>De3!>LlSH zN=}A&2W~TLALA`}H;f+zRybY@lsEX&_gFUuXUUdJVETE02V9Q!=4`p7QB8noY@zN$ zk=xbS26f!AZ=&17iL+3AJeL-c{ty3OWi-1@;@y*wFcCTrcX?1fQlb6!Ais&vhdR8m zTd`O4AgfzvTxfiX#)?F#{^#eN=qva&mVjeJ77Z9N4M;3;fmTc*JRyt!w0i?QfZv9P z0w;zB?F6}`a{h=7o2`x}h73oV%|0VxPr-MZ_P%zK??mi%6IpX3+Y`_`M!vAikQhUH zk?DO%_F_@=p%=^^M%|oYfD-Q|xeW&*o&2rE67!N`fxB`sCWg5+Qo^Mu1s-yzF9j@= z=uWj{)*rUrctx$~`Ifg-f4P#jLI!Rc?Z1PncsMQ?Ug2e#kK8`o(}9oxWcga=n9F~B zs6Oq#>?Xzg43aG<_AK|zGH}0{UHzMhpAh{X zL?|P`*)sb1y%N~<02QD@i@MxWFj)%}o%}*|8-P68a1v3L z$s;z+1tt(K$&VlHdQRWc)3&ANAaZ=RxqlsQkEC$OU_IG#mEe5pL94L3+pC6oL=m4I z6B#_sv4CuYQ}-ud0zg8E(g^S_>d)hxJqaxaCq8NH(z74GaWLI>c_f+UU}2pNb51Ho z6lyp-FdBnF4?u1lF5$S^EbZP9G4+~6EA-d%rTY|Og_B_@EjXh%Y&sRYSN_YX=7V=S zXy3opX&`n6BT1fswx+1C_ePvYBzwl&4Vnh}#~vlefrJ`T_!?H`;?J$jrP2MW!OKH= zi}pyX9W*xfqAUY%20IQm1MndP-kDs2mOA<}MJfZnC8p;)F6?s5f+MqXU}r1-m*PPb zfTDwx|1(D!fQ*BGV0$OK^+Ivh0MX^WpKSmeLMw$zBPU=Bx?50N>9s^Z=*w|spl(W_ zx4-#bn2h2bMYnCmgPpfBL5fT{Ippw&I~?NKYiqF$@kGA~F!0Ks{C;Vx(je_lgSVxh zs%9uW^<(1c^SmtmlUN9v`9H;XDvzFgCSft<=dogIsc|K2GvyS^Qmr*#|Ir6F=BR zZO&j*Rp5%T#l&lyW-+Qvm3JS zUwCy~IH;SceU%8B&Y5Jp=FPC5M}r!5)>$#X#p{Fq0iDBX9+03myR_y>IbJqt|A6{} z(O=_=GD%jSj!L(quLMMnbPN9T2-2NEd?9$#z?A8qXdGGbva?$WizHc$M$M=vaB;IW zD0rVnwW$e2eoz=U+Qp^v&%o?Ze+2i|duIO^lM*S&Dc+Zu75H4>fSS)Wr9)ri% zK0%HjR-yy6epf0(1dR>gEFKaC_`!jc8EJLok^BB6@law5BiW7~n{fB&DB1s&NXTT3 zOn|cT1S04NHrvb_F_w*8vkrM2acy`i0ldfE3}q?Cy&f1%fm-;ck4>nh8UurQ};7gv1$JZrJd4p zWI|u)ig6j8v*Bbs`cS&&XcPx(eqibiZ&yAobbJKbD@0~IGDJ@tZd(2h7Hsd3c2|98 zWZqbJTR+WvZ`%%5{g;FS;ADAKMryMvisS(BAgiT!Ks|mu0BZ8cN&+(!uJ?IQhT@xR zUvEQgbF>3A0#vUlSZ>Gxi`H{S2s@_aRqgYAI%pnUTljEp_8DLWM01~IHWJ@x;W{}? zW4vXKnxBAOGlo~&gljXlgwtH{`R89ZFR(sN{HLN-%9P-qB|n}Z%)VeQGTP|v7aX^S zq)pQKL+fP>_gIj3odXFTp3{E%6Y)%Np(&yUy;Vlm6@U zGF#3pxD5)ikqW+V{EmzxRVm+tZ~9~|iYOU@Rrf@qa+=$|4<%S!G~07?4;kP3H9gJs)l1~44Jeg4KDSN7=@ zZh?{niyig}RDVf$g@63UYuzA_d2Uu8Qtq>#=+5GtG#$a+A%_9EO?!_p;xQ=UKFp?9 z;N4<+3>8bz2XGQ5;Q^vCDo)=!EnYJ&eCu9_s=D*?e$1W{0+Gz!V_m4sM0#)D&~8a} zu5G}l2G8>*&=x{C*t08$^tMl^PY850jS;|OXyRYG*uZ;Y&{agJ0CmZooS^U1zMk|O z`%GCOOk|)MH1KqfbYGW17QQ*UZmVgxX0`fsz3Ga4YOGCctFH=5MtF)(0{^FBiG>Mb zq^-)lg_e2CcMTjHM?cSe$N+C}Q($q(UteyrS`8PeXJTW9S0_^cOnX6Yt=048GWOuX z^dDp(R07`1ojY&B!C7|V2H}i|h4W{zO$vHGzx%BCx!U*4+3HnBB1j|dtAIn+5z|IO zaVV!}+|<EwIP9eN%+gVM7F$DBqWSbhd4?hg2KFp(by|5@rnY3kSXp`J!yH|Qi( z$57ADpkudT$2GY`AfpvFt5?V^kL+2;8>a;;lGA)g1eWC6xRV0t0&f8u7y}Rm7OmDE zJi$}r;6%mZJVsX$$Bx_?40WZ_N^r$QkXh*eB@FV{gEztjEs@_RUXsF-!x5lQ+1LDE z2NqKn+Mw2Gxuy~P;p{U5rN-zcE(rl+CjZK9+J5OFKk{als%=}5+7zULzX=|91aGNB zI-sOyP2%9=Czh)ySTDZqag9>5ql8@< zVFdNDqHic-j{3s;Zw5_2>Mt(`CtHrY$@9fN-}5ijxmU;CSW8%DtTnpr`)a=DTY({u zo|cv_-fFQBI4VvHhktCITZXCwuigY>+i%thff4styl%&tW7G`>v9!G?7^_ZEpg9eCRky=G)5PAlP z05FKA;tqnniyq^CKST==RqUWnfmM3a%UCw?4F+Wg=^q$5wwt8M^NwjBl)H~ew*SY& z-%`P|UIL}@<^;97L$G#ylq`FN0Jw-75}##)d}1rXhBj#wro67BiRz*FhiMWR67ltG z^|3mn^F;JHP`v7e;AY5}1S8-xren~y^`B!>s_~eNrKpBy#~~>L5>{md)9@xb2EK)} z3%GqsiH!|UCdvVM=V|~s+Iw`VE%}V8PW;qhNvpR)Y1fYB1A&QEo-dorCc{6heqT{g zTM>`$hrm$$f1VjWL`p>R670Ip>Y<{*hV(h<_9a1XFc2-ZC|V8oOM=E`_6!>l4yPbkJ%fa1x$ z%G+wdwfhK2G(rlw>yy0sf2EvlzT5b()fNO#K@IXh8-b&1T6w7=7dR)XaQZFL=CC-a z6Sr8Dv|}gG1dn;!JA6m>%eK&*!J`zy??lf6 z2FEA>y~Lu1*MJ@c`MoTinGt0`P=-*7CbbybbF-;St>~=KHjV6VoO&tA^~WV67C*yuKe01c%Py$JT7tw&4G>scVaCD%c+>{oo$v};Z;Cp`*2oA_vP?ntLoeM&Catu7WE035b=kkAf5)T%m;to%#;H4cmDsRJX~r|=loa53ci#tz$#vt>$wJ*i>cwSK!_ zIajkCSq#7eZR`T9ZZS248^HxEAREiPZXI92xw2aRL9{) z{EUQ|u`?#K1VjlPm&^3#Yd&Q9ocFO~&EFgI{CqU*=!P}WE@ zboXoAt87st$VYQumYY-dvV=4<5mjEUOmk1OfKXOBX%eYm1!*Cs+aDb>;}zLo+TJMj zPZ=1=6i?vYfkA<~HQCZGaBw@>xhW}gPmp$M3FxiB!C}v$RC6|cP;=1#0m zCP@Q!l$@lo=jW&hS{FBz&z;J*^4gf~B!=DTTd(E4jKfx2Zc57y_6NNuF#Y zdcePm&ya7G7XCC~9RGnql2we6L^=CY|Jh8ESICU((V-_9`ALuos09VWv2?zInR`yr zc!>AK1YTT5o#diHFWlqjp)bIoNAtL_6@&{JGFb|c%sqmd;#uJAcNG}*bq+AN7%h3E zLRZDf-=CrvJZsuOXW>Z>K=J=KS{x!Y5;aw0T(o{6zMy*km83i-&ZuDf(~%S+2V{td zVp`KZUwBbj-*{Z0*w~D-R}em;&TpwgErjp?T*Txm3H)T!QkV1A+a&!ot5~PG@A*Z7 zTbIHU^MmOP(lEx6ESn(3_qC1qJh>}e{AULArxVyI^fuNk7r*-E7`XO|PA!}DX-Y|t z7{hUgPeio88#t0);xx+?FJIgl1&YvD%*iVc=3c%-3}6fJr5kCr%-yN#`FY7Bv^Wz^ zuJgDijZQ?Ns&1rQCJOCt&s z)Ev1+j@i@pl?Uy2I#BF0Psm_Yp*+-Sx*gEhi8i~uf(aFiVv2S_ORR5z%`YoBQepuqSAebgUBh8C!jJuU5R{>L9 zETx*5Q^qJWS&i}!x`w2=lSv3TO(LIH6+P2LYNoCS2rbPr&TkzuY}3!stm4o{Q@_t) z$nF!@4xbUU!^W+l9mG12=@Ldws7Cc$!hu-2R8VK=^ld77&-F)zx|mX*ZQ>5kN0OlCf^69V%?*gnC;B z%wz!pj?XK}M>_-sp=yTYBCP#`k=t6G2213l)O*Ty3`Hsc0aq|-q(}U;)lb<%CK62I zt}3tJxuhi?Ae{7rqRRa&*m4L%OD4$9fu6>)vg%el%jRrh;n08i#CZiXihAp49Y{)o z`|XfU`(dw(rL33aGUM}43BwkMI3p4YUoZhNs(+uqc+=UI*qN1D^wsf7mnLEX5qP2y6*MF-QqmLUG;yh9!d5~SNkb8PwvgYC3c z4l%k{tAYnOH{TYc*VOi4m;SPWK<~j4u6Q|mB*w^iM*egFJ?yAWGvYDb4jT533Y0>| z1SZ=nQ4Yu>#_az<5k2BgA?W!%WO=a3?X25aBgqo`{xp3>_NrT=6$cDsD5 zP!Q_8Ceu)(dIKIJsIZ(w$Vnp(Ccaw0N<8Q?E&>pKqBEB`@?mG@gzR1E^V?;=7xoQl z*I+2aDuNu{-k&F`&;UHh`<1cXKB=l(2n{vTu4A%Y+g*Ml%apA8A49%EVWYP5H4PlA z0RjH5w})jXVFisu(z_XeS4}7p4oe}0uKxr*I6-o?IO6BJ;|FA*E;1fxAXG{!rW7Kv z`*!0Oo99UByNPXtH%;TT2{3=0;0C zasuTCl++Uk>=e%H3mBgHEv3-YRrN!Ik4IH+Yh!>Wx-*jU3&7*1aJ``Jv!I(h39h>m zYdGYk`Vk&39q-5iaOkz}fS?|tee&lg&d*Jjx)3?pc+scK;gDb>az>YUaN9+R=J`Wb zV-L`WV)i@DDess=I7Ky3ucM*R`Lqk%QTdCYkBE9VlJQ-zIne3oG%BsOxG0LhT$DXg z64m+dov@6(T9*kt9V6i|bXYYQne(g})h z;ctop5!ot*!MvE$x9ncw`s3At`^2`>k{ugRm13PJ;3s!9k zig})3y;<*iuRh#tfuxNpR^s$vUdjMH%RKJYP2JetL@EaAC{K(FAS6fCSxIr&>$%)T ze(1VMZGLQFE@H0PRu}0y`M)pigKM_O2mj;K%)orU=-x@t62O0h9nbx(>AZT;PFYaU z-!3ESy-dk8g}*KpbH=t(iYZ(8k%EnrRG5v|ac#lG^MLd!+8PUXX5M3r_U{{(J_77l zy({C`pKiITi6#l4)Z*~Wye-dr=1mrPi~tsCpmA2P>t-KQabtJu!v_a|WF^b%@ zPq!}4Ox~qpWo(@(`R-|*$f5pTJ!QTVjfm)dO#om^*h%829@er&y3M7CGn^eScPfWk z->POs6RHpw$F6*-B4$v{-nFHggdl;kTCL6*KQk6df^#cx_iZUiB#Fc|J6% zuzoufObRlkDFl1xR*^hc#}E7+BK7r&Qt9a)2*ZzKaXT`Iv8SQtJEm#$b(162V_UPX z<;4xggQ+G`S5=xy%SOa=;WSqL?~ARs57FL3KMs2gL5GpVD-M4(F#o&jl&jf^fhqsc zg-)vaOE3~Rvr)jXhW16@jLec&+jkL9rUNNTN(Rl@L8LBCaP~QXYTWvWCAhfAGrW#F zeCE^X5GGD80*;C~xBfF#eTEecryKhO-{d5b8m$7QgrQn85$29aU6o$9!s?cnQ|ZrF z)bKfe`%`)>C&#FO;W@$k#)PJ*i}HB+JB=gxe_jP4ETQIqcqKvtzfX8Rxbc9ta)6@= zyPkDhzRGS$lH<8H0^hl5P)UoKu@dzi2E>a#;}X4K)8o4AG`|l)5FokpqeZ!$2jgWp zCLdsKNFjuo6xI1q9*qwYpTV&%sFmO?;8K#Ze=37g>lcBjBogdCGTvtlg5W&7kXYvT zKv=~U-#$dRYbe~(pwQ@oZ~K}sc#b24`6&9Xv_@;A_Vu7?pGs#p2E8JZcW05~-L3UjRbc@St`l60$=+&QAg1q4N zZ7lw9fD|WMSsXEa(ip&xcRIA1_=kr;N#QQcNim#eGF($HeV7iU##rNditIDWCCOpf zWs3|l&n54SgL?R|Gd*c-u^^}Rj(*1K0Cteo%ZsJM1+@5ui`haf4Kgkxsg?#h& zMo~NqfzHF<@NDTJ3YQedaR5y~vcD&}u_`z*f6Cm^TwU5Lrji7G0~sh^tY5?pfaK}M zR(np*;#V-J9&{3)v!InPB@)%b89S_Vx4b&KXRYNOiK;%E-u;YpvtQn zZ6vW(dI-aB?mU17wY1i?mYOY0KTs|3y{cz$z2CBy)ofrqBsWQ`uv5;iOjv%VX7F-- zxL#(Xkq#ivx9xRzze&tjHK;L->%8dKjAY`DGP02E^-6Gaa^FIrsX>r7R^c4X40`cO zLwsf!ol=8Be;x@SGYUhu_Ue#qx|!?WnUtVI}vBN zp6P2s)DP!e#CK53+ojJ-1E!uguMTvVJl=!mf-z{UB-=^ku1y}-^^85a6=Wi#Q-Z_c z<*H+8eg|*jDEZVnT@!S(#(_68MhD@Vbu4)OaQByciV5V~5HI4nBzzZmfH zqCk3KC7HjAT{gGiKHO&)4x3@Porj!FNX1g$96-uyKe5ZYR5YRJ#%b)R%4PoDK87J9 zC9I{q0zUVzoC2(uFeep%8S=s{;2)qtkezZ zw_95A?3jp%0~dd3)47baf10B=sjlyg-kuYcTUbSEu_m<0tDyF{o4pjCXSN0ptG$5C zvEsE$H4y)doy@*%4{hfH^R?%JU)6bBla28-W>XreMxC!_H<^aa@Fc`)s#Agdj7q`i z_pr4!oe7Q>if3LjGhUHO(*668=`=rf1GS)aI0m|0gZYa7o_a)0B%=kSC39S&LF%sVc3Q0?68gkTq>jHY9VK zqho*=8->t0IoqG%_}c0zOPYgIUHCzhR{HB2wVz67T%Y< zozzWy>!rh_(#3rkX=qkh^^yA@!N8%4l=Yk+!eB8%_?qLH^i7=2P#8cp=ee@uu$1;> z-L~!W_IU`I0}H^9>jNtyQTiY@A&xquRBoU>Qkd*Y6SBJe$ldQs_xFx6voV59cgyt0 zo)i8rfXqbODc3s`e!M+yc1y^t1z+b?Wm?4*1pRJ>y5tNz7V&MAy25{eCTKN=X?d<%KFx|se56js-+Zor4|loCEi4~qg0ZN1 zafVDv0UMs3XQ^oArGThaj$P$Ib_|T^DVY*iNe?}?n}}1+MaGBxYV!xZGWxN%(b>E{ ziN2T!ph6g4VGp4JI|Fcq{A787U#5pBU-f-ljyiS7LN8XNXst z=eE=~o@JYLMnFleuuJ3DPf~>gZSmsL{{(Fn-hyB5N2NhLlrdjJ1~P16ST@sxDh*J> z)|lKENs^t-HY@0BIH~SrPiM2C2PP8w!;5jnl(E2jLw=656|+IYU>3>;CHgOxG_D%J>29 z0W5MK)!J;|Y3M`puztig?4$mJfCKPFR889bkn9@R0w48mNumVh03$>Rss1G?ZdLej zy4D*P%ZBm-EZ>Yu`L+&xe@%#_?^FdOMsyw}o<}JpVP~PS=!QrKJ&=6qQYcKSOVN zwG@;vCynr=lXraod*OzV*G)PNkca4rN!C35;mq1##;a9WD!ynnC&y3}~kYjJ@t0SB?fLT0|c3RxzKjovvd^`zLM*@w#p94ZoRO|x(DJQO!?*M?zX!$>aHqbdDkrc;iNP5JLUGPIH)6&sYpqnnI?1C~GL7X@{+@tG?PANr!g$qV4j4`D7Ebf|j z2uSPwqX#YLFv6RZi?4f$IO-s3z_M1DmgX+n@Bbctkx_`KZ||d1$*^9yU(Xo7o(%>UV93O(Ds)&dXGrWyBVvL(Jk>Gw2Q|jB~4+-IdcxF_@NE>af&%I72!9e zd3qcnToO2J%YR?iq=>_cfuH`;kumZv@uyFOU&wT;oFY6QaqE-ew3NN1r) zaNEzAFTLzBu7J6i&{a?bn^XB}dZi03(-KCKF|4yI0sQ6Dde=xBiu^H=2Nhs*L=5r0 zkzkXTTGlnRWM|wT8DahnI=o8x+b7XGH1O6hsPa_D5PPLuoiR85!-dtS8MuiV z7+sJIf!I0!_ljYd^v~YgO==q1fVTg{=f~a1+ts?3vUDx%rlI)iKazRx}e{V2FMoYjVbb4K@&z zj6r)jbnF1LLCB5?62=q>ssJH^(77@1Wj8W_jkSAQ$GJk?2hqV_2ub%A00L4#{QC0K zosDlbmRj8%O>DRZxRDCCXxz!&1odWC?JJzf$>DpjuNQ$~G+hn06TQjD} z87MVu^k4%NU=O970gJr|o1j{{jwZcT9@dF3YuUypu+#)PPIon!Qc{_cjO#tI*mt<{ zZlSw3=hr3Yi>xWj=3L}g3tNQ~sl9=0q4zQef%X&KVn^2mH=np%IEBI59~TJwdh9oSB4`Lj3_Ap|U7Rv_ zEDObXg+T8&q6pl>jEp0!AIz@wWjiST2etIl~O(xhWKhY zl)7ElQasce^nQnptQYAORyM1!3-uJTl_stFfW`0a@&GQuXxQkW9aPW&eTcEQz{^NZ ze$F8FjP&=uJvy4t)&gTAPOPxFMykHxS;~~CNMOv4Bip`LCoEeppGv<5z9)X}B=upq zuU&m!Zp~jn*fw&Qq=5J)Doy|&9+pYSrGQ&;}NqF&)LYO1W9%;AsG==<(ehvKcjB?{eqQj{^w z7{wso;aAm(nb)>Bw)I-5t#9eHo$t=*fME)%4InSP zBXk;mW~8Im8ez)})c|8sP1$VMCk;tzVevo=N2@-@#GHcWTMT6l+A!l@L$-{ei}=+lcXdST*fGH3`8j~R7{rgvea4m{-xP_8 zvsFMC)i$~=?VTgH$Gzc^M%PW^<6b*<`s|cRCWI|%{Fa;_SN2((ec}cpW>k$Q^v@EO5C9H~_J39wsUm1GlQZ8>j8a4x~h;jfxRys*RcYcUE zN!E9F`kGPZ5wCPrtf~l4=1!lh{>PBb0ILefDes0xb=O)cTg*Uxl3k@u7V{ufA*mf& zVul^ZCN_>UjR_R}60U^|7;1B4mZ!PAF@=>l=IQ8?u@+UB9W8$jXFM3hVtmE-PV?tN zOqpg|RCw>N9OhN^=wA3)h(|V07b9n#hi_ua0B9D*d>M+_(_=0Ttj2?Evh{hn2TZ9< z=lWVj(7~^x+t^E(bnQX=FzUJ+mQ0RS|4MxqkxeXs*w2A0`ndKKUbqwU3@fK}e=H|2L(0}k06wy&hKxj*zf(oMB z%Y2ke{*x!nJbQ<~CM_gmpMBmE;cf>p4d1EAB?|oN9-e+FagM{+JEjBsF1%E>ruvnR zlN#E!be1Jdfg;@;toWBz%^2;6Wvi$gM&+@k8`mlB_BSh`U-3tTN?%fHdWa$MQ}z)E zJXH#}`R#DWsjg;ycoY$*Pf{+ zpb!R$%+;0v0$)N!(BSv-7C9(}p(pK;p0{?f6%(}xH3+|@{}Y{0Qjzw;=C4+GQb{QI zF2J~O7Hx)KO07(AUcYHf)QbZXIBM)%x-A91r&26kg>O{Q8iRS(Lu!I-XcP?S zb%B+zdI&h+*l{=+YI0v#&`c&uu~tDD-uPI@Ph_O^=C;O*1~3>WHkq6qHOw%;TuGuD zVp@x(N+_Twt;lbL1|4c)nA22bw{_fX4{${d-n72fP(W0?W85aop%^BHqQ4gxPs4x7 z{ODZsEC84k-H-|Cw9WVk7J794IsnVMn0G%XL0=A8^&B<$c|D2PU-Qrj12LN&G4eQLYLj%tIR5v%M5Uf zP1A9!Q^VB0iZ)yJt$&l+_yY=$+L`MhV=Fq^FAP+8PSy#ru*~R64Fvd>c%z_!r35NS z!Mqy)a5&}(pF?@5i7O;wqKD(E2G912q25kz822Fi8c_p3wgd{D6(oi7;uW-Y$oK2b zyqvD(mCyEJb|WPlP4Pq_?%Mz1q-rn+9?gyVbK-4km&)4gi&LDkv%N(p{a29=xJ)8u zDadE&fyij>;A{WE3`{hc0t`00S${=WplMRE@ID0c4Z>Lqx%?Dy-hZOc0&JE=GVs=p=go*iqGS}(oV|j<8|Fj;}*7pv>=kW-Q>i{WH2EF4NjSWSmz-Y3)I=o)W&imkN}+e$z5d31K&&&vx~{As3*)BW#BOu>Pe|= zb+7Sl)u};5QUzM8D>&jM(2xwmz<*kB@QM{j^hTgYd!pDepxQ=*C|@M1)+p3%bA2U7 zei7p}Y34h=RT4K>cmFc#XA_A-;l%b<>ji1yiw?tN1nhk+SF}Chp8iyx5FX~FnV;O6 z1+7P*+WfAn>3u8;B+tzp`tc$<(-Y>TVW4fa%^SCd{84H5FMCAv+JRU-Ng1@`!9nVh zmKz8ozuFeeZQ%Vqf7l$-Og!*>qWZPbm<36}A?*fmc(tkzSP z&3Sb8$d_F|X3^?Fe(HO=Vm;3-n zDF5!#daIIu^iB?F|1p?y^>1EbWuY;eoCSi0d94A5(}5}7Z-G;DQTPLD095wnmXqgH zsCmo|$n=yLJfvNK{7XaV6X+KoKyn$l#Tj;dMPv4g8+n-H(RUgF0BmBy0m3%$vYOn=P3=WXM5f>5+d9pRJm|hY&5?+Iak{< zyG?hG7yu(73cSxdz~FwvORLu(u94)QbOJU68;YA$*sLy*xCE-Ceio}jlo3a<=)TpG zmO__b92Mt1>*P7KnuE{iv{I&qfXUY11aQ8=HsQN^g*)=loD12o1FndurGlHBuwvk?6bmPpIVw_c;Pl)OCGYjckpu%$k%guD9yMP&aOVN1CA`)iW@dhd) zF>^AhglR3PtIr<9qQUQ_fFq(x_g@N2zsgD>MGzU8T4rJI7CGG}`y6HmkOCgD)A^C3 zz*wn^jOCL;>&|L0uiSE0AMXdPX;pC1b>QnW`v^Z8J1sXsjQQyRHydvT#8ZFe_tDwV zN-)7Os;l z`ie091D219W7Is@&H{!Z4lv_AU`A!dpjxV3~r4Sr}>cK0e2fqndfb5enz+`A1Ce(q2`oA(3y$Bxhl? zUoN?ImmZV~!oh-Y=MotJAA^;Q%7Fj$+Xcr8Es&$LO0pzbImIE!yDP>NAO9>Qmr#LQ zx9e~3;C)=10vzL4auinrxA1$phWLWB_DXYyV@)%8jRtOaPEs{~^&^tR6frg24v8pMqjEl_x(5(!eYL&3k9E5Z(|L{Aqt+ver=h+enjiLd z-SBtT4p;kW@p7r*KsH3N)7!q|;y811Bf>3evmLms6taY845-@c8J<|f(>m$xiU2O! z#L%0iGgL$tP`*?6m@h%=D!d8^Ec#oiptjZTc!!N4hHYSth2`G3CTB7|I}`svcDBs! z6h)PmVRK5ztOK@{{)n1o-|y`WpAV8O#{T@?(AMX?htMrhOdM49Il#o;NKct==!}xE zKkz%BS2y`3$JpGMXKP| zQT8E=qE-@PjTww^8ZC5Za`59qp@(@4-yk}i84kE_0p914%1#F9;~yK=76()t2861k zQS3#Mewu>{LdW1$ijDTOycdF7xg-*>1=>&KZz^*ReTz&YW}Rk*A-|_vd8(X3a|8%m zF}62hKc6}IeIDq2tna*rt1(`;mqQ$9bTES2hY^!m;ee(7Yq#w7`!JM2Jm+_^S!+(z zp2Avej&cCT1zE)AFEi&(4ru;>)B~^G@^MFqUGdROFZ<_9V+jnU?LI3#xVd0d4?jNE zl@MH%4Zs8jLEs`c>IfXd!o`5jdSw9M&&knm^zD~^p3!u{&3WThbCqE5fxD0-zC&aE zei8rBkq#KqK8p8!KC{clb{n8Bn!fYi8LfOA{kV_K{Yjr93|I_T0pL9q{^25EDb!L@ zYWEb_Sf1r`H!-Tl1?0M3Tb1FS*#DP0jc8fwN2Qe_1`=`%TxBFew3NhOA7pMV(*pny zPk1WnJ%|5Htzk;+^-(c;|16f5%LH{!VPFHNz$bG>3B~s=twTCd$r2u*S#{pA=}vBf zGVvLV5;gu?gq zPFqA7u7|oj-30?vS;*cB*rlltNoVdDyFctibPup(>k=me0@^2rCNBWS?rSpSGrz{2 z2StD@AkQ+n@B0|Pqj-6#;SO;($;z~?9tr>U5eeQkwGJA#AFa=O?v&D(dC~AlzK$q* z-M9tn3halRL8i5x-MbtyrTB3v0IU9wzeAPY&l%Ws*KYFm0}aw_JbH0s4Dt?K#Dh_^ zNNfF3cQ<@6^SMf(lQtWE4hTwxEw854)WlWPhcS;1hB9k?Hae9Z3V-aFcJTrh2Sb0i z)eWLjC}AVXSu!++~dez6}R6?3Sj6>O63om^S3_-L@Qd5QN zDN`5BBiCwzK9t^91lbnJ}XQkc#NOl;#Wch?28r?OPu0!4vnkbaHvDbWac?*CZ{44(L z8eJ*p!XyK2;YOA2!H-2n04*<`-y@`J&1eVoy1#wgARC)zl&gyR2QD+_;g;kZ_3x>B zJU#u>;$~}Ma6F*TCH6CA0hY1=CU6YE&f;Z{CI5khr7LuW*xw|K z0`)yXBvij9!R)MgT8orLu_otePHm;WUQ!fpmgVQ)4@_ole6IRk@Y8w=9nXEmTtGJIHdTT)PsL-K&P zEeIa&HI}U`KbyxIj#9_#{E}YYS9V8%8Jll4?M{y9oLCum8)cbegK-pY^%a3tcY5m6 zn-NLgO9|an@Kq$7W4a{ZD5^Wr{;csoJvD?JC#^z)xK&joz>GlX5q6GdnB$oSM2Z5p zG&5rGdoPUD4qJ1D32^Llko~4KOSEk%i`q;@s-r=bmW`e9e+_N~4znTyA6eW0aK=X| zf8Txoo=k06dI=sNe#SOn6{Z4R|3yRX-O*4kYoS!ePMwUDUT`%vcx1(jn0wAiAGZE} zRcXf8hI_?MTx6nS&H_EC4sP)f(VW)h*hqpv_FU70au$bigkbt_`*)S%x*=hQ$xfrW zoLisq;3JXRIaxBU>#o$W+kIlHYGBFFQhfa*F7!^euN@Si02z0&Ucbr2fqL{9$Bz|m zJC}acfFD0wAJD(q5i((U!rEf^hh9%Swgu$-8eAl>tYQ^D)EkVml>4$T?Ce$KS!hB& z>OD8;PsKbTMVVfxv}F`k-?n~@()j7wmB1k}XseMZ@am*a*Bt_aef0ThNM-YQ{{&M; zs+HoSIW4lUPfQt*&u5!JyHH0T(=57MWR4vn(3^@7-glc{C&nC$$`0<@J1XsqbP6mR?3w5pmY*WF1DJo|1FbT zWO5xJq>16C(JO{lv!E*8w84FwV7C6SsT_VO6p(4R8xU>3F@4aa$^^EATZDTAqGp-d zUm^gCdQH|V{3I{R96+JlmSAQJQom+e$;B#VT9uL=PDU91mhv+0g1?(VNw8`eEmNnl zEBd7e^)=GU{lGFi;xI#Ukr7r8{WuRAV>nU2Jwk?;p*_ZN&))`VQDJWI7(gVgM?V6*f7MJsrPLQ;i-kZ_S*LFBApKk*EP+JEOGHAkut9q9eb?s z%f?*;J&Siu({zT6%1G`N(RE*rGSOubK$o-010Ur%zq6_r8qhL~=Cn08FeG84er~(N zvl2WFA0FPHm~^F(OTjN0F*%&EdmP@c9YUf;Amq*!LwZa6J{V9IO$tknEd^i1}aj2UY{baxfzBk6(22d z*ZmrYZ*7d#bft!1zx=9&y#ps>RZE&al*_kDf)CDCzIG@%=>%QcqQNkY#ujyyuN$Wy zXQTcAar>B%lvAYV2%uBaJgVf~4n)3h6j2|TCb}tk!W&OOLIFMQAGCrvQu_1K;aTnS zGcr-K1>!0k<{73iTAG>DhV5xOG|pwWQMNvyziK{`3W|E$?KID}LoD)eyoIu{VydXN zg&@8<>1u}=hex_%4ck}SY9Ii6S?JWAk4u$KPcRVT zBEjA+bG3;F>~OXT_@gPX!W(c?;tM9W5aM2%(6y33T}W1n>3*d3f+iP-&#NUBP_61j znldDw_AR79HmgQGRB6pb>{vLp=nyj=j zZJqF!V{Wx?>pelh063`?sqS!A487w)q>N_DEN#F!yH%ZTYY`Xp4cF15dDS0DoI{qJ zh?O5==O55r_kaU_Jw8 zmxSexI(%%dkvm*!AkxT#qh~f`W9M52z|#&7Ds=B{-=ckY!{KnlDx3*Vb|xAP^F%bA zDD~7^GC-_H$og=h8drUi6kXY(@<@+F(x+V7<(3YORyl@hCxY>q>ruchJ7Ex~@YyFh zJJX|Yju@|)hNy-$A0X^Ur>igOtw`xs81rjY7*xnd^ zraF}PEc=4p75)5fR{!!F*8xS)yGz8`HWI`@-sQBbi*Pq(X;VdfRk@{kP(}xXwdGs<0NLO?jHNU}q#8 zr_F0U`dnFqg`elX1+e_jOxLU>6FOfLFBzu+6OZ{C@U@?_4o@D7DI{$+L7z7rU$g_@ zw6f|3kt$j)C`yk8+vwK4VN@oNQ#_4^Hr3gx2WuAFQ2s`HfsUfhCzPWEHF-d)jM?fE zZS7-0!pg&soT4=4-DI!7EExFGO6x{*`YJic*NnWuN@^nrH1#{>hR($V%2P!egOCvx zTTa?she_BNF!_$b^ubI6*8!8b)NUKA=EHkgmkObs%CjAAEG#;%?iV+FGL z({k^rW}If%A~U%QDbEpoPI_0X3he2VyLj6C0I!c|Gdkl7t_{L%>En(>PwGP-Y3ETY zA2-Mk>`_|;xdXvpzsf+b%4XJtHWu%A6OE_-Svv`>cpca7BxAK%;atKTp(<3sWi7BY zNzLE`Jl|ov3xrf`j7Kva@AAIUTxFU0pWgkKD*TZLa?R+Ya^*wVcIY78n399$kQ+Jd z^Jzuq1WO z6fcEutqR%Q(xjkpc);zlD1V-jM!9^53?>>Sm@$O0=)!ZLSD`d;yK@?6M}Euf($q88B|hdf>ws3 zrJ0bno#9+fEe}UhqZrWS?|!X) z(4C0{?(fHw#5J7T32Q_cAH(_t)+9BH9rI2nwet_~&MexZ{~n4O5O4HBBJT1uP^e=i zi>VPhWf%InFhpeZR~W_U_`JbZ87Kv?U1=wU&x!3^0iV6Md(&Nsz;B&Hj?I`T73a}Z zdEstl16HxTSY^0!cckMV4Hjw9wQ0RkYuSa@b*#D1bf2z5cPhhc-s|9)93vsk4~&ij zIqe7{tyX&0^oF42R^R+naBq6(Jpl(s$c)%a*qnOlh04zmbPgByYpe`T-YM;=v#CDTqWbc_`@N;WJWJ>5Y7J&$p!K6 zJf6hqt`mYE&QHYeQz&CAGdo8!J&-d%FGXVK<+N_p^M>;Z6nhUdtx1;H(sP*9o=oXn zH?k7ZAsqO)AgQ@;ZQz!w2ksZnwDS?i)sZcCe>%S*V9C~LhcZ1KTsrxTU5Q}M^En)X zD+I$ERC=w}r~`H6wXP)=BFR~ynoYORS03;I+xK9%i+k64XpZtVcZMs{yqug(wV%eG zlAFr3RineJs>WRUmjcdmB{Vj#DY(cBs$NW|@-{Unv2(`k(6LAQxB#ZPg(Y&n9ns0X zfPO>(H`(uRw!odooJ$4gL&?$~HaHXo4-h^Sp8tymX1i`pYVTM%({T|#MeUfk-p3&AQ-7VOG>R3j2pB&9bA9(W!K zuF7>yE7HJ?(jRoId2HA(4dz|*O&;_A`=0!q)Cl_l;4ft~kGvCH-vz_4l71_Ml;olpJ+FzxdLsf&`cW-Jj+0f!f|LcK{kNKWbwz{SBaK{C>*S5YqKgU;hfF4-q z?R;6t@Q|CN5WcuQ)jO^hmzrMLPXRR=ucZ9)T2BvT&&z+ByGQ}ul#+Nx0=~=X6eC$y zMrlJyHI^pneNQsI$SECKndYm{s8w9;{!+)VmnY6#JGTaK&U;GVT5Zq&2=F_nMjh#B#W6N zhYJB;h}3ou6QEY>UDg)eW22|f6t=g7Q5O|ALNhv93FQ&VbZ+L`;CeX7AsNOeqv&4o#gbJP%RQBey z`C#^80ygr@0OhqewKYzq{YG|Gt1sDX!Lx2KV@U3ck(Wns5HfT>3u*_euw8t&LGu7cUQq0AahAao(lLV2TF{V96w0M4RDbqlB;GUO$OTk3iW-{M93vp>dm~Yt7;_0;+tJ!b5A10!O`Bffx zggkO;1jHrwHhUp{BjqxWTn1UY#v0jHK~&cVBv}TmD^=Of{Z?p3vV%6tYJk&+kN8W9 z;MoLHtfqx=*tqtKV(9M?WdJ6Srm$wiqj?A|3zZ7iFN3&VWB~7j{KqRM#%e9zTQ;6S zu@UQc)7!KQl3A61)d990DtVqP8xvxM;@jKDy*7r2YT%F{TpKaJyBX5=dAUAtVe3`k zua9irI8Ary&O#m-dsV;o>aY(~`&`rTlUoLfl(I7*=Y)K{({+!7Q~x)mGg#h1&is6g z_T7M3>R+5u_K*qk2RvPJ|5Zv>P2DNE3 zq)&>pid{-Hgw{fsV%EaoVE;JvU52I(YJ-$cVaCGI^UnrD>YTr^fJX7Cu)4FDd{WyR zGA%c>dLqLJ9FnD z+9Ju|*G+*kpHlmWv(emeipr-wy9yTW=Du?h@$bW4U-%sS8c=Zv^}%pK)3nq(DQYD7 z4fO~W9TTIfu1gFcDq^pM5Yp`@FZ`2yq09}c1yw44v0?q7w0I!MwhDv{StV+z!9eL| zEx8hNDTk$`ln6FgDJlCiP3=elGx$$F@G)GQR538PzS7CJ5Q|Fd@m>C_nYi3Eg!>UJ zkKjFbEhM2Y?qS_aoP0M)6SSU5n$ME*a*FdL*!>O_;@$OOtueOo&7#Z;FBi(&v%A1D z7KkUx$QJ@*Z#=AcrCk`}9gdH>MN|`29ETqHh~SplYpa4^z&6!hT3+KLs}+G~XMsgr zVEd_Ytd#W<+wNb-LJef5)~4e-cqDg{H(HK}IgTwS1t~lmvkJkJyny&C1a5O+ddfNs z1KVsT7VNn#35T^}YeU<0OU+WPII8fi=%}}KT3oz=hp2KVW_`$e4BGdqx;&F^*HY{#_u)H>O=oC8rj(eYzLJxV4EY>>)W|+(Zq1A{eOR9$MM9UUDh5G8W zn=Z<6Yq5+M0zZ(ECaFL`4U4h#BRyxmeB+IZVbV#fU(%ZLs6NW*HK!r_2<#Ns6O$#N zq{iJnj{EJ*1K~(@b4ot=nsI`CRmLA&(GB{tamU|hZPzpkBYt5g*c~L>V+$es$-pbB z#2>^V9HCx!U^pF&j(;|XCRZ$oAOVtLSySG3?@#B1hjpDP?^M?fq7n*XsNVuMJha;U zG+qUAKpmsABqY=}R79FtJxDFY3mS5s!j`rA{K5aNt}n{f8#aa?Q&t9+2vt`B;IjaXt*p{rb1%57u2^~h4dQ6HDd^C(bVSQE$frUQiKKLu+Mw#1ES7o7y3&jA51_m${(X8|>-EX!o7wqMv))?{q zSCDD-2wHhf6oN64uK6|}LOAX7EYYK@XX>$JY0Pu-jtq0!lAr9KqiLtnSfv8U`QHDN zr{(hLFQx=OEh-~hKnwNCjT_yGaZFxveCAW}bQ271C!HF3yg4qbfX5Dhep!KczMdIm zteMy#wdKx{F%iK#^$F8*g>85 zn*uvuNu{#oM2hBOvR$APt2*M$noO*<7pm~Xvzjr{Goi^TUErTu_iBEgkY?ozW=-7= z$n^tBnzP^Q_{#98oawJ&zSkKJL8^f3put}s{b4s8hV8}T)xb>(uGvO$z& zmHokTeWT*S0e7$dy)Oy)7Kc7YV2Jj-Z8UGTmLW@ygs7gvvuW|r5@nGT_$isR8H+^_ z;yC~eHIeP0XT8XrhmhEFR6zIU`>W$A@qo7kTm?iC{XU>ZK$e_`>*lV|n#_ zQ$o2A`Vo(NP^4wUY-5w$srn8T`5aahm+AlGQmN#j;~pomsv~>7cFgVaVpbUkn@##G ztS@=$8hLQK)RSxPs*hW_M_Yv6i-SBEo!lj2p%H_Tn~hm0(kt&o#iq*KfNl-506-0R zdFU;TZ(V?)9FRmP1o_%lo7u}R76+u$EM@5~1k^%ki`C%UtW_%xn1feDhMsh96FpZCPX;0hc={mA%6oyU zZWIy^uN4C>G^ zRbD?()snj`P!9q2c_YMG0vd3Wxd8dQ`WJ&GK0P9=(8fdF4`tDSp%l1cbXgPX|MOD95HzJ=N*BEb;*9|e z$Q4PdqsAqiui`vDERTMrzwOW_i5MeNC;5E;gRXBs?jUHgDEBx#H(_+^B?Luk*e}pf ziM`#O%bBkl8DuuhW1)oR$8WX~Zu@40 zj4tAOQ*J{ET>5ux&5PBu@_; z8tH6;Vg+R_S_3!Z-8ff6V@7=;8Me{ZXU~T6zx@*9mz~o&r06Kd0^T!)_WUetv0ZX+ zRzwFF61(!JR^G53R&lwbH#f96Qmxdt(5InIJ+B!MIYhm2Vsn{P`HGr z|Hyj|$^ex2=TRgusX%+S?yFiZ<393l05SIJ^Yv?r+F`#L*f4GwKu(r^ZP&RX9aukX zP@!+se^IlOUwDak6E*b_X}Ajlm0e{4TnIn6;GZ-B-6`$#^ACDq2eeX2m4nk)Y+MRt zQqdgH%NLj~sf~&R^H?7z-9N8dFZ{dElW`Po$fgSC@4c*m=2lM6#&uNfSw@2F72&(a zcIs{m74ib`!R4Fe5Juj+#`W3uPE0&y`vk#h_Z2_VGv z-4i$AtQOd5*QPf&Y?-G5G#v7y8s|b_bN2^a3@8rTFZL$a7-AdAO2HGq`0(*LTzxQT zqcn@Asjx(Rx0ncjKfg)nZvl#IQDV+W$={`LP~;fYQMIMj3fD%~=F&MW6 zQiut#ec+*qf`U?%ctOJgU`6EBqi3$7FiX zN$vV|*#tNdWy^`Cg>9u+Dje9a++&6o>ocIL{Dnq1AD_ACo3%dUOkE5Cu+Neq2j23tAbrn^ie%7j`Ad z^)kWZzN$iORx$N2AyDLKm~L#(1v%b7FMnM9%J0+DrWGOE9en?RYns8)2jWu6>A^PM zc47-lV$c|WA}FI(_zUOBObXZ8oEgJPQr|e@9lbG=+pZ8~>?95a@*L}{DJ*652i{X?y08+3gZlO(V+oEZ} z?ITAkkjf(%oYGM;I5VX%yY1=jX0Xp1QD2On9B%#+U|GM-ZeW=>>$(CE%d4!qR5n>f zt5t`fjyjm*oQ(%NIggE}L;iL4Sh^`L=n1vkJTICjjx>So7(f4uR}a4)l9xm#(u@FH zWSueG+>WeLIal`7M7*DrAW(yjus2|oec<|m7pwaXks~@{M?CCLGQ!J`jo1~dB#D7V zg$0_Mc0JejO%u)wyadK0ntPg&Br6p~!$Gd{xsVP8i%&DzOxHDi?hd`t&|!NsOm8yR9Xn@9Ao6Mu=-NV;A`Y$u2Vd&-Z7NFSI-}*3l!`X96*qY`-pk(1VPu z?qiOxaGGYJN;`|i_INwHXJPH{z?cF<<11crp66a>G^pF?N~;@%i#q$BG7?7y7#mIs z(EZqV~##s1d#ufTjNT?Ji64t*_i&v+>76Xk6mBu#r1B=@2Y?~zSV95 z)@M@N?P?v!YTt(9n$%6oF{vl;1?qa2(x=FdQshu5_`o4B%d|4zf}cki8eD1#HId@K z{0`10&r^C82v{T^+B8{o`)8P@-N&$=>#=Uf(2LN@Ts_J!NsD93DI z0lFe@qI+Glx=8gn7q77U+Z&%FERMu=M_)q!$g4`^BlA)rfrg*q6MVT5xAhYvf#ztx z1RWecfy3TiJw(BkFKY7-WM}05o1Cs#Nb#mOaadn@RndM^Kj~3}q_{H+LYxgOX9-Op zFq7Wxvs{Q#j0q(&mgt#Z0t?R3`7c1G?QFCs~8_~00;rc(3`16!~=qg7QAqLbR_-$&>@+gRXY^UqA}ApbC7&TSlXsNFb!)TBKd%p4;gZ^c*~n_OmjQjspLm`Byqe8RaSkPv>OCY zA^z^HW|OwEfOS8!+E}s9xxH?m=amPYEmK*Ig?zGQWR!_K3HOSU5pT>VJ9KH~{G@UD z0C?c^<;n+@%=!BqW)omhB$tyO+*5vX(I1bY2F>H|c5s$LdTaJZT!hgvvRj;3^$h5a zlbE;|PzrMjLquP(8%pxj!#2x{+VHMcM1UXSgNRf8%Lc|Nvt@QuMDo?OZEPQxFvV&B zEzJS0d*g09J?~FL-R=P0?ySQOmEziQIG6LSsdc9U6lCxclHib}41T?6`)^tf_-E7W zvHKM5DyVTvC%C@o=&V`3PQ6FN9STS3!=povt=X-d{*rf^WHiATRg|6smigX=KN82o zq-wrXujIuB+}s9I-+g1I(zdmGk&IV{=ZYJxh6?^0<5%y6|BtuK(;EzL$d0-%<~!N! zUghHkw=iuWih~T$yeB79_b5ocXaspR4J=Te$W-sIg0x#%*5)7+rgi4MQJ{c!>{Nh} z4BDh*fePS(3d7wZ2^=G*p4oMeaAeF!x!`&&J_VffZ<2+(!}`R641dIn&jn&RkT?rc zz;4Vm-qy1Lr~8A?C-%7wNKDb({{_MASpG5IowJXf4Uu zJ<0vhTVu2^$dR3EUfU(&)PL^Ku%Py&f01W`*Nd=|l+;Jfoj9ZM%>QXzO|YB0oDkuy z+c@SStU0^!F2d&uPwer5n{HDL*J?xM_uVj^M?B3I1)lWU0x{FlVh5uefr=e3G~aws zYub+U8!veS$u~a=R{9#zCUkg9?%xvbv-0$6`KwKD*5V3}LH!niPFuGMi6%zfHpjfW zr8fGpg!WgZl$)3-o&TREUUv?D3ndlJ1VDkT+r~Vm7N;#@2v4fMk=xUiMS4oW-h!sC z37AW&1+m+A&NK04Bf-UQ{C>s$0bzlRYWbHCnc;GJ zHTXn9qi0bhIARfibO0F|`#cEUjh*fAX7!AxTJ?))pUcOa)ty6hbozxqzaW9$iHc4p zvVl?QC|XePO;Hnuk752ubj95#BZkTkR$;-y(*yh#3;d+b5o3$y>hUF<@>#V6$M?8t z(f;_hL*~QT?aGX}Nt;|KRBIR7RXT|ar+FTMb3$|qxuP1|@I6}SR`666=kR9C_fU!j z`V->a7G{VuBXyxG0zhViVpr-`uM5qNO0UMYu;f1>L zt<1Ih_n+n{)jsVBI?W##THrh25`5ghY3e-G2D!;l6cpuZb|RBxcCg#_0<1ptH+h`U zZOYn=d%hF;y_Osd1ZoYJI9Of!tC~&a1#0xCw9IF?wrx_C2ptgYQr{Biu||39$cAw$TMH^i13O>k@Nx7j;U^H~)lwsgt?j*+-LfBk};= z+_jA;j1aKENDwgDNJe~BZ>L0~X%UoAS2TU}D{Ui$<#s2>xH9cDDj88QI@=;WR^uDx zelc)H-qGR8-%S`;&oqpUM}7o>a7kwqf8hT(F3i-zcm(sPB_wDgh{1Kr-n15KukHN6 zgMy~=W`imn9oKxXEn&Ob)O@Y6ahhTcsi%qw1yspGD*_I}S96Iah83Z@{}YxZh)j08 zPS7?{*Pwsx3hd51?h(G&Ih-TEvr;9Ah_(Ql=$C&Wz{MR%hj_{lXjM<$(XO&JyVmwMZdY6U zcmwwgwypq$)}YT&XaFybcnLuwoni>`AL0P8O4NrVgHSNLOnG)|FLE`$wUd0;Q&bKb z(EoME%vmUGXhzJ0`DJ{6czcZ!2fd*Nnr)L4ODSoW;=TM9jY+Dfw+``MA& zuXoOXcuTysJ1rZ~Y{A1}!5zdff^CyHdb?ipc~91FQg;Bh zbu&L%2brn9nOG%kqR9yO*Y$JA-Y6KFA(G@;LTw3$CVcEajsh)a9GutY!zb&|@u8a0 z;T2$5vtsgHfeoikZ)2*)JvPhWA`o5J9iLbwgTUxMZx2yo#O5KM>BA^10O~#MV56 z*VsqLR4Gc#_=aRRWaWw=FtZ`*?t#F8K_V(x*a=Kzl>3d`5|;%~C1j&Si#GC%KG^OW zO^+l6JWFuJ!(6pr%{ zSVaY&YHFc@kTqD6mTOi2h#3h4OTsB{CpfoQnw!4A9=5u>w>Z=gymGVD6%GP)1&EH0z<=+PYfA9WL=qD% zHO9&#lRHa-{C6dS4_}1Sct&`4L(YNvCh_(oz5J5hTU_qp&w08*vt8E0z|$&zf)*+h zS+$eX4KUTUy(smVq4Zt;z$MAsyUE!@^X|gXWyw+4KXbx(75&URC@ru@woAFfVTF>V zJ?d|si=w;1CtZD@H<}VgPznkmS0of#_Fc5bv3A4tb0*5U`BgL5tu0qbgXAq{%FX~7I70c)s_o1-? zZJ~tplT3q0jS(vUl^M{?(HF2Ak+fAwbkt2>rd9J6%AbR2(b1b4dcU{wgLN9c3}v3${p!!dx+_C~doQCV z`~WAfsu8BF>jF+yrfO9x<%O597w}@)X1UqT>aQ)YN-w*XloOzq9X*Dq#RUsYkkN#+ z(BH)-6eO{>DiyWmBSc)^H)&cY@ylH?7Ts%H)S$r6rCz=M0>63LiKkjojd40<509XE zmv-emw*#20tfHZdgF^u(Nv*SIf*7wtS(^uj7zIvSfCSIm4*r6>q6C0$WmQXm#-wKz zN^OTRh^KlMIIds?(rpI0ic|7NtKseP@+W!i)~G8!*` z3IqHAAxENu!o3BDz~=n&B@%=%!uIcmMS(g<3{%Doyb$gS7T-#L$o^Rb!A@;JQjuxR z4T`PmCqBL4*~qG>b7LrvJ5F1PP~ExE-+sKEHm~TpkowkJr1f>?qIPN@;lVz2XBKXO z$SFj=WVNyajo0u?Uyvqdr-=qjV@kHU&5%Qmon*F|eyRQ&om!-A9EGn2U;^oDoSatk z(y&If$ssW}_Z_S+)o&u$g++jQ4_SYv6wu-V+G4J`jd&K>w&|{YUBPI9u~_vV_dE~g z*JuD5QO1(IO=F|9s%wF>Y^(A8DFXoBenc9UF$A@X?U6V&Hs?rC-{OiD*@cP@+=VUW zeY$LTDNmORQiNTLiHXU-`mwmQ*83(}b9o-uUVqeUrM3!&sF76Z@bfMQnGe_-1TK-H zMhveD$rmM|^NTaFYzyorfBKUONwHTP)Cn{A8l9R051%4HgRlEk^qA!>T2lf@r0%CCXC z6f;ZI+lyM2eqdoTng^dRc?BUg26_2G?6D^f!M{hyj(B;1et2e);e07rh_gmattWKr z0$9kp4~%@7uA;@RYvLb!7p$48Nbh4TT5nLI+967T0?TFb@4Bx6SJbaPans68Z)Bhk zbi0f4Ew3M?Y@^jnKWsA%gUq52^Ji5)tXu!QYjha?;ylfUB+>iPJDUp);o<7|-Ba-h z34G8~SIm&Hk8;^$4y>0K|F&JVl@~%X>9Ps7(G9mskW5X#&M&lANMLG)K>Z45dNLs$ z(LjX{pn$8JM_XYQy4IY#Ri zJIpd3+4Ak<1#41gH^$m0LI~W(oy|I#5Yi=;_vV0LD2xK3@;u3$;r|%CL}~}cD({^b zWQw6K4gGanTM0UUnUI067`%qI(rID!N1@Qg@>W$joD}fJt*Bga= zudaIK8@#T@RA@X-$g={RU~o7-u#cBaYD4iPR@oX|S?%6030U2Efu=JA@cq;vu{=y%*r$+K3OY@?Glu~f@FN5p5v z&(%QxAi~YBQ%PD5#izhltD4W#6LTYh6gB<6@NPU~$O#i_UIP|TXoCc1yHTL@4+b%X z{Z(5~{f{9o#soZ@`7~xeGOs<{RhB%Kp+l2T&XdK8YAcD`Qb9^{PN*u?f1G}T;e!e+ z))&RQ7I(cdqfv=^{!4)fN4wi;j?|Q_2?Gq33=PdhK(-hxP!$Ji)bzynI`0J+;QP`+D(^xYE8WNYGNTufqN>H>tM}yG)q@I<7T%mDaMxgvj4uQjM7yiqDv*+v@Gn@>N35?7B`FW0(K`iJHU52Qc)}#^|{2JuS zs~r2DXL)<=OQOezCTdmW#QsUVS7%L&AvrK+Qmg;P5INr+LE@!>sV4QY#P1bjbYwV3 zCWOG2*4Fr5l+dGq@A8B!jwu9jcL8Bvm`kWhjSX{>ANIZMzosnTB*aWAb0l(-McI6; z>{%anrRxR5r1^y3x}1PGh#HlEaA8yTLO@e0K~z(sQ%NQ$g)-L{V{I8t5vwb*ldbDY zv->)PvD5q(wbfQ7YMoz9(w9CT4e-Y?di9(C-&VtCy?9fQmUd}~Tg>lwCi+M62D7t- z3OpD*B~bwFpeD?Aa+qs))PY~;8G~LWTF}huRP;Tsy?wMY13MefPTg)QFX<`I8g4*( zdYYD&qmPdXTsA}nW7+R)@lxI-FRHqOc}p%Rr23H9nDw?RK=0B1;+=r?_7b%{yRHdO zd*(z3HoJ)XrI@z4&wB`2#E?LAQ4vz9S@QqQT;F8Sxmjc?t{>>GE>I90-lDXDJ4nvj zDrB;@tvjsZV5G$T4Zf z58%nH5CxtOubV$;0k36jL0%!c=g{eFQh3UH5gZGPT;0BhwLYSO9@Iz-%g~31h&JF_ zp$1t>O%whlsRKTSli+vx|LXGNCq*@`h;~KX-Q?a~#BQNRhk(Q)8M>{UECl1O^T-5C zOL92EwwT_PNxn*S92nDrm6eb4sRsg_Y)Q9%P@87_<4nxJ+$=lQDtY7KG(hP%OO=-U z?SQkSnF>(eE~SU>_gSgGb3L?-;q(L??~iC+m;W_7rr!Odt1A!uEs)S){@_b zNa?I|iaT#_DSLG+Tni5ouzazBDMqj&FRNLVb|%?c&#YeubZbRqQ0#$XtzR&Es!iI% zjeGaa5FCi2)(v->{I(Mlo;Zn1u=hJ~J=I{6$7A<$--?>XJykQ2dXiw=fgh2KzOeVj zFu6mys>}h*GCMtf#oizDE6*UMYYmwwg_##2p&3jUUepSV1Upd{>-cm_0;2Xn)2JZM zM7BcK0n{)J!re zBt~4^PI?6SElQF}W*5G5e{{ImorQgp{!MellQ5P(fIdY=BYFhCULWxwUHt3=5_na18v#b8ST zx?B&Y1*~R2P-|o%V_p6ifpbauBGNh2eorl1mL$;1^SDwi$efa}U$MF{Q~+?7p7r!| z2C%o}m%Ws9bb=UX+H|T`p&1QE!=s5IJcMgvWE!FH8f2-8!zoAVCe8i+l_@k(#4m8r zf}H>WO|(T1|8_LJSagGUbBR>NuVyAgM<%eb!5DZ2#$a#jD0CM1S{>ASf?0Uxg0e(` zf%bIc+s~=~)4<=@(3)5-cdQg+5Sk943XN)~J^rM>16J9+72rbxkvrTRI0vg%q;nG~ z9$r`rJt#3i9wB1R5QP99xE+m+!^&>zy}g*xp8=ICKlNHH6EGn82v+er!`rJt{tC-X#**+w0u>boe4F=7HR zA)b%OY|~DX>>&U_h_`2Z%>%($9lV+se8%T<2_KkS|iZXp9fsZspB6i(u?d|@b1-l8v~@S;8@r)=y-EoDJ_q27xbwOVc1 z1N4wJH|YW6JpF>;|fWlq{`WIQIJjDFmmfz&yZ_(a)E zguL*t54wUltt_uXZivRy4GSFdQIy8ni>~W=3mhf52|@g6k5ZmIzYb0vsj{EVkCT|`v^jVUAWB%b4K0+V^^{fh_OPhnwJ8)vX< zd8?>!8j)li&k0crAaytzW71%L-Oxe+1YAT>R@)UTa!9J$Fv8#THK@q%;@zMNs^EI& zBZt)@UbIpRLn|gGjoekX>CTV1y8{m+Uj9Mo%<8KMQc9+?9kT9 zV;Mj+?D}bx9EvVbJ-A2!8lf=(Qy4KH1rf}DmF)e`CZZQCQdr+=QDBUoLGo}KN$d5I zL6Lb6i~S<0&9$$#Z1TYHaJc6#+9sNQ19FahhsZ43AnbR_FoXho^!N)q;+1Fq3p|Md zF1YV%h1-%#M2G~pNyN_u-sI@j4JEiY&k2_22BrnTYZo7IJo9>Y|EX^S`Jx-^=RcaX*sb`42+ z@30>N;h~hcUrE((l&@%G%extT*cVA^Oqx^s0n=+SA#WsKo=H6jDwY!6uS8DqUis9^Hlz-K4%O6#+w6DkL36c@%mSGnu{OT2m-dDf(`)At%}R7{ANW5IJE*;00cgz$=p4D14T)# zIM?u^%UbLl|1N+C&5TA4RS_Isqu3Lz3L!8TR6l@Sae(dw=oZ=}tuFk+Ej9298dsS4 zvK&kzMGc69Q@am#mVVr&_m01Zg(TjL zKv{JZ3$@*|osOZk&`%EahbpCjBh3sZj5$bXVL0%yfBQPQ;P)#{FEteaG=@Te-h0ln zmFs(OU#>K06}9Frui@7>_NsHgc_Rq$ht>}o>zXx?6`OyI9^{)C(AeOqGW_8%)w-R; zV;ubNzVL?E0B_4G?0h-`)_@X{i9Dp4(~~L#yZpWC%;Ps|85*LPH}Pk8Z^5f;n`tKB zVTWDespXG-vaS)HvxSlZ@Ptp9&FbIb9EH^ro8*go1`%!Iz*M+6WGR;NX~I$29;o3OIUX>pS8YSXeOG|i4ECpzkh9eAVEb}@Vpno9kQ6!uSvHG# z2FR*F$3?O4(l$wjHrspEsRarf(=S~R4=9xIA?@CYv-E+Z;={+o82?y{n`rfbrCS7S zu$4%MAI}@?KgOl1uNf+5+?LgWrmHc|8lyZ-s26+9b_gqR3JaxdN#yLpdBLIw-lE zk3#mp%i7uAyH&XXz{vaxJ!d)D@gj;2>Ilq{2e>iFd@DOMXo3d=nC#L8h<=r;Tmg)4 zObj}SNL0^pdfQ$tN^B1#w;VL8X5P8GP7v*J&XJ#+7H5SkH@>+}WqD^}{;|9soz)#f z7ewsJfQ%H!WL{h3pK%8JgW=Lf1Nh(fL1#}@sRmNl>V)$Bcida;gQ#Bq=Ak+8M$NY zGyhgS(ieqe5#T)>88_Ch5nA5{Ed*qEF#k-KF+QQaaZ5C`>t2B2LFG1K+ZiosS8yN` z#L>Lwf#ZgSE6U(ogPOW*<-_KjXu21FhV84pdu9Y_~Qs%RsIMqK+ z-cr5^dVCPkswf>ui-#bmDv>}aCH$k5CFS4r5cn!^C;~W*vkJMNib|I zPFs22BOchDsxTiU6ysLN*x^$&06MnK(^lACmeDGIQiX3Ef>(#BxEIOq&r!y0|C(`y zu(rQ`E|Ftnn05)Da7QJjnAc{WvCaKZL^~Q&s_S%~7wTVHE3|3cnm+z0;>)Y=Ot|G) z3FGwoowL4~7Kb%2Y4DYp;%Tu^{uLSi(U9@8#I$LQZyOj+oADhz2$Jp z)yW`jZVb*sZk;a$+~XE5!&)KSK?(NeoVJ%D$t4Rej1`XrKd(q>(lpG$a?RkORR0iA zlC-l>=>ndSV9GQe`J?d!l|#$D>RfldROYz}sBZx3))N04x}Sg>&hdV$Ij1kr@=Ibj zbSl({IU<84%63QAGi6ik?=rTT^js9%!R#pcXD- z7~*RK$nN@7lWL|z$Wjexb`yL-f|VS3LHjxz(j(O6(lRI&Tj84)3DHHhg1=t@8>F*| z+PwC!7_>|xP8IcQVsoE3MdU6EO`q26j+-L9k)-Sw>g$MV{UZu--HOEaG;3!D6~xL* z8Ut4ai%LhbQwMZ|JJ)`;4PSDZf)MSI!4WXt|YpskXW(i26M&plEw!n5uBiYLCg)jogEf>-B zsiOi3-FxVMCBZ9#5IMg@?!+u%U&mTE@0r-bc`+!{JUF;z*M^4rSA?=v$${qSg8qKW zOccuEH%E&?lz!l#rO?OG7RVwCtstnZch(M*YdeQ{{IZW$TG~wWh*o+{AgxSQZ?pq9 zwZLk!f*g?wFTAb4H05wE?@q&TYeT9i0*2rF@9O$TVvnDmH^I}ko=E{%HX1ixfr9*$ zN2|%)IK-~9BH3#R7iJ^WI%<-^xzNZ7n!&Fv#a$049H}F(>}e&+i;5igiQ*xkNXa)K zcB)R*g_-B#^rRHiYKz?}?q78sd`La9oZM#^#9V@l=xa%bNwpaGwTmvmTLJ{#cVU3$ z$}P(Jqd_}L(Db*yi?mk`qu-dvoaCop*AiolZnW~{rZenPo+fS%NpiJ1bFTK7#Gms= z)z`fbz)d-%vwW@SPx4XlV~eGVvQICi@l#fn(9DBanjN`8 zVpKn@EbgN5D&QU|y&nuW$61qKLiwhSsRW%B(4pM<@Bbw#1z^XD^&i@jn7?K+yT{9z z;Ylj5yS9%L1%`aK z@hrYn$`TI?N`B(Zt>2*@gpOV(ir|UWa*=>aK74)gMP`v{um0OyGdp@1A@Gsk zey&6d{%IQav&j8$xAd7w*I7sCW}pMQnkf!uNGnBC^j0S=96p*G{0k){3iL9h`3BXu z1tU9_XGWL4N!I6Woc=7UcTYy*!a@$;-6J(>ge<0xE&@Tk7WwV&8>Y{oS4&MM^`dI5 ziz2?`8F=u>UvIHKbgPOUvu;_`Sq`iEtkC$aTK)gO^kBK+iNTUnW%*qhP)`z@E%MY%M!LTYU?H`w%hO4Fj6_WgTJigmPqI}z!or-7ky;PcI z0O#}Vy-=$8kA^S#(1G&RIVS&Gs3%ikW@hb`zr3vn@@;}snN&%CBC10dV>FpG`{GLe zVaVj@zqe=_&4TDKDQ@-f_cEr;ZfBW?F#+L`O*wD7!2+G5RB2+*rVYmGB1AWuk6VE1 zo4B`))|Re);<@*gFxV3oRjINpjq?A(Ls@458BM6Ym6l(mUkK{zb_J`YBMUD)^L$Z6_i*fL1 zfHd!gBKIL2jNz{H@>Uh|$#&q*zjpXT`&d*SMBP@jq=Vey8szg&eH9cLOD&#&XD8cE z!YHAMWf1g+^nSe~!!8$UzXo?K4$D#6KloLw&hTIJmLC766k3`Y_I_}ibgF=XYAeHd z744ckCAG>AEuDsv!ah;gfS}h2(yn}Kl%&X=0`ljA6#Rk?>*X$)JB1$jX7d3B~brcdpiT9Av z`qfP&z&_aGge)V~i+-%X6D=oWF}H5orPuUyUPJ3QJf{C}gFTZ>3d_Qgc8@mk(ZQ8F zNbtCbof}o%VdXWg{C+_<6R46wF4j0t0V*1NcVI8x8m<1M&QXKI3L^QDh0nGX5JIrY z1HiHmQ?>O0>dMh>pC$Y7!}c|VwubWGa2+o{W1sPLc!0Fq7I4n<{m?OK3esUMfrY=x z_Vpn4vIHTYcWVT_p3fy;BrwUU#?3eACIj#cE3?Pr{h}OxyGjbw!niS_-oN-6;&($u z8jdSpgPJb4j3_L(s_L!*_Sy%Ks&~QEEiJgK1$Ib?#9QNQj)%g@Xc?G{iPiJAd> zYd3LTemC#gU53bNc^u!+jglgLCPwA_VBn4%7&R3!t-A!{^gcG$jALOvQVknE(N+dq zUey;>J@^R9YKpIODqW>;dc~VhpgaS7I?SV8o>`H8`lNk!AG{+AUgsS&c-3P3InA&6 z{fdBax@#5QbC?&>)XXmduP{*7eAsv8L6>ppkz3!(vtxQ~lkKsU)46GrZrDgY!)#U~ z<>K}!@;lJxclQ{#=7pmFR5f`RSk6GHkT1eO)3^MN-DSPG$hk7#zue~>M>=^ls})pmn3Uy`t_SIwz(DQ9s^q~1+b!# zv9yhENaAyQ%TF8VR)9uahS&>Nxlk|hp^?pUE+zn*#NiI2r5T^p`Y3IPPV%$ml}xfK zn#j2P{8V-x67T77vxwsu=(%Z?wnA>}SWudf<}Gl^L$8dG!8hM#0M3+8g%81}^iwvv zWMc)MmZs_rb$}OAgcLMcc66XMQ|@4TuN*&No>0!^+nhomZyqLk?v$R+(u5heR3Wc+ zm~<=O$-*}HV&OEb>w_r)1z)U(X~4KnKL319F|k@b&W9f)>ShBX^&2?WP7_=NyxAI< zaK3F43SV3sJ*^tw{{LE_J+X)IukF=OXLDe9G|Nu}cG-viA3zj3lawH{Tgo;yp z=M?m36ZP<;vz3-bri(|#z}^@pY?yli^!|DfnJJ0Uay>ilg6JU$DDnpZxg6b1z)42< z?0P!HGaMY(rH5?}QmJgjOH^JXu9Y&GGD388UW!kdhPb(7&@^4Quj|oSeOSo%C*xW) zRSv-`MImReo8Ohhr*Ln5WWSgZiAc!a4`PQS5}~k{4EetR}-|s88CnUsDoh7PfC4tq_;{kOKh|d=Wk-G=R$Wrm1A}u@BIjSi9-6 zV1jtOkINrdqCBIrODoV+NAF0AtBUk&PKshklC-5ohFB()m=?Qimd78BUXl)PEn`ya z0CBX|1}H)Sh{PR-56U)pF8gro3p}UP;uu=}_BDb{-bmUl60a4ES1!#Igywe8(F4^5 zdy}Ho-SgB>C4HQfXN8aS+9}zd0|Xz!7IpsIway#SRTp4tyiTMUrM80YyqR!?Mj4sy z)l>UMUVdf(!WSFUt6w9 zObJ4Ca+?1Hsn20i&T^Mm;3pky4lA;=^UQdW5V&1IdRMazK&Jhi_ZrqOWqAD9{6CKF z*!L0i#VYZ*M#o~}laM%v$=k_aTwh?yLarWUJWK?CUa9@Tl*&!Q%<@4R{j`S;8m zes&pTlaELo{1t)Z`Kxq&(F(TS{-8@YDIs#U0tk)40f;VwHB)@0U+70lsILJ`_j>gn znIMl2J^D4)KBtjdu9C(ZYKyT^i9{e}j+BZ!b>I%KgL0I|js@l_Si-!Q!b>`y%t#IU z5%W=As2!!qnUkm@Ibj72DDbb6z=iOm77IEz|K+s+_$!8L7f|=*e<<5cE(6u(&XYxK zl_F&=ETU@0kPhDR);FrM1JLb1S#9rS`ad(3C=TsD0@q?vAgsWxgaV3p0oKjNYrfr# zUX5*-iq`i6OYe>HFFK>xqK{EVaX{{@tXe*-_N%sMQ^cD6+IDYpT%ZDaj{@;_H>)i+ zUFnXvj2m~OzIjh-^Tv=M^Ihfx1d5_itpAxz-PD@>So5#fP^<^+i@oN`9H_rEr(Khk zSnA?UcIS+Or7m`^e{zH2m|WR~Av@h8YA`o8DP;NHY*E`s{g1u!!ZJ=jT_k1**;$6Y z5ZYZbZ43PFpeYC+;Q!>T3n$^A$BxVK?bK3h(6|=jqLRZ!l*hHYJ|X3kn#wSa8$Mo( zY0EV;$yJ)jO@mPs>C-P)uIY}7Q0^$cf<7|li}leaR?x$O0jV00K#N~TIQUC$i3rn> z^ff(pBZ!G8m)T?Jf1vKWGV-&q%H4FRcW?tJA^S4cXR?2B3h6;WNN+|kT&$^seJ{VB zG?ZjJt%~R{jQ%va&v2?PD=S-xFRa`YX!(y&o4<)xT<*_ zEHX4T)&Tx?@(o{w{g511i#hPa-C7o~jBUTHP+}q~ucZ|MR612(Wco|ll6y%r6 zitaPZ3+H!GDYl;Cnv>ni5vmSp;)*$BGjBTz&g$W!l6yoxCKiwKYy25a*B2|_#b{hm zMUTQJ1$2Ae4g_)3<$m$BIC1vg7wKLS7Y-1zvy(>PtbjL6x=xpj$pS+-St^ks&NUIz zLm3_-78yIK5%GVLB>~O)qX40@(A%-g#X>o2KncU}{xQN~;4_0R? z9vRgqx>cFeNox9}{t#rCxMcCl%Sl{0h%z}5l(14v6STyEm>Ig}O-#e}4h9kL6LS>n9{x{kPyxFOwFBCtK3v zk~s?7v29_9BhzwCTf-vpK6(&InfNTRWynP7K-psR9Lr_vsX)vo3wNo3& zKM*FPN+=A4Q-T(_WC4=zB)yp+0ZbD=41G`bPS{QW;rsJuA7F#{I?cY+Pu<0n0%I5u zsOrUKP$+9Q*AJ({8liF>a&I!D=yu^8jG%)a*rQ3Xes^2+fV@$x64SZ>T=ADZd0t1> zW0l~>5u@aY$75+(BL4||VOByrnK||+ri?n`ivBKKlVP}Vyz0imX<7te-?LG{DgNB% zxs9AM98^)hf7{#RR;1-R4dx&r6P{@zi zcO)BG=n3Mj7eI>-G+TMX`1mqN&jpls_ClL23{GRjn>a#)l9_2l|I4?F2XWN&&@JI+ zNo7A6tQSi~V}HX62H^eZP zjO|X6ZC*+&2y%w5SA(yj_dkaCpnvwX(*}o-CW0E`!$e(-fU^mr-k#12(%^tc& zB-YKPWeH3fMG#bvM&xrP>@MVl@w))i`a<9Q3Yti67yz4R?4>9UEQxs>ZJ*Z9k`5er z9zh@;Kbs!x2Z0#>kpUo;MEZ=KMMy5#S#j3S8ZI7DL^Wq7mLq%DTQ3VBB4_X{?3spT zCFs+Wl~}Osvd753;H^&QB*Rdq+Id{F&&~g{Rm!$dOwr)gn1MK@uA;LTiYaiYe6Fq} z;p&)F!0emR=o-GLWOiFQ5h<$OaeQU%q{is8sJVfKbXE5?;$ z{hMexccIecF@_gfXb7r+&k^+ntZ*2q^_}PeMBYl~bs~~A?r`Lo)?#ru&y0Mv0_UX}x;@@5y? zYOA)`S12+HY*nZ}RZ3kayU}KdKeL6bY=62iQ+HJk<3xlVl)j3rhg@vde}OwTqBncK86;ediN&iLf@7P&v9F6cAaNI-e596iMxG zV%~M@%pcmoO%?=LxnOFk-GztAZ!=hVJGrzQu0&hoN zW5n5oh6D`qxJx#zVyN#k71a=~VNhVc=R{r(d81X6Mi1Xi(|e2j=A^i%8)x(20NubDeK zCDpA>69NIq^pG}N@L2b_gt;D@wt#l8@ZfyOH&L!6( zsBv?V35T<7CtMfeQTy~hS_`Rg#BWyotK``eQLB5^GQgd$EB4{WMB&C0I&jdg9U{fM zX`}d!6HG-WPt_JT27$gK*{N&uZJ7ogEzuPG{dt0V(P^*y)wZ*p#?gVnaN=Ugtg`4n zr^TI^1Nx7iIZ1HsVppdvAEc#Zz^$CWk8L>=pBm5FC1ne5EOdJ=1Wp@+oZ~>*JHG9k z3rTY+pW^nH?Bi@YlHs*N^&s+my^w8zI7V|a*83^R@@oJzc;sPPA*v#(MPHo7F; z^H`-%GLN>VSCIOzchiXO%f;-~6rEX;HpDHM6P4IRU*gDJH+XHhpL*$W5rf#?o2usB zoSe7xy-?w2EhFfNyEIDtF8s^I7oaJig!q6}(#Hd!{#}HJ%6-UGtN$AgKv z!^D+;<9+*kB2A(^q~Vg6UPmsd96ilU0$bhj2hr$R{7;CL(7xd5J}}jjjj~TRvZa9N z=3iZHni)vMytTIt%>@HKo7Ay(7u}*Lb>q#Iq}GnrezaA9N}b_EFf+|TXY<`(-hi*5h z{g@BuU;QNE92ms@ps51|ZeWK30ZtZ1Gh>XGQcS%IA!L8hVQ?honSDg$zrEsXL5=hf z;9@@cqP7q_35R=arz5=u=!k>fsYkX+oTv7DF_{tow5<=7vmUhldx|wQ4N1lt`gb#+ zxk?d~Gw4i=)r%T1FJ`BsyfY(%dvYU?wvt1&K$MqlDb&-YF&(%A9;hVit$>xetznk? zPk!?!Hgf{9R!#zN6R!nC(WB~D_6RR6erE(5HA*0i^5nCVbyl+xawX1^;s`zl!i-1s z0Uxn2namP6XYrVc!xY#YG8sGgE(UD2>W(LkY5>(;12VKVBU>@n)i1|zxY2>T0t@b* zkMjQuE+K_FL88oke5Q=t8S8DyOs~mKn5n~arEA|8r+&ITi0c+xoZ!-hvIGXQW_1^v z?&SR)e5KEnk7Hl~`U+Y)Ls<_?NAqlIkcX9`1eb?w>Sz~FLc$kgX9b$vEV717|Eu(0 z8vt3~-oF5U#xvra0cP+}==`a?bzTN^X+GaTyg<`z5h!Yq18( z22#Nl`d>P7Ow%jc5eIn3X)agncHb-|ak zI=ldLk@umeDAYzKfINv&Khu!20#8*@Fi_aDKC#z20SY8Danw~*)3xE8T0YOF$u}<% zDT~+E<&^ba8hVn_C=HCZ*)gd92G{heZ|T$AvhfzhM4PBorv%IGbeLxF#;YBu7}RHjQayaXGU|95C^EBh$Q_C-xt4 z|7T^dZNL8k&CM*)o*!g?<-3O@3QfgMF{xVsto>-r&Kn8-(ryB#AAa`*mU-kw+6d?e zMal1GD2B~X3IOfKHye#Jf#7oZyb^_6`o}py7vYh`v!>AI5$mx85nKj;U5Md~5(efJ z;i0R)ryP@;{5ni5j9;*MmgmBJZySN10Fj2wu3dD4?0m+g9)Wg%lL?|wNl+f2dPKP( z4n=84MnZOQALcj+47xMj80$X{OUICpb0fqy9-yyBiFAYOjZtY^UjLp(1DMIG{e&|@ z7QL9bcXt5s>DmOtnmC>1MNOX$Xv;4?=&IB&^#3Cx3y>Q>pz>u&fNE%DlO&v}(wA4TOWVY~r98NVqz)&cz3Rb0_gdhKysh|QEl!G9wu!&s~^`+w6 z0}V432-*KRE*YNGoqdl92$*)QGtTP|lx<@WCyjWBRw19#e`lqb4afEO54sRg?DN8( z;%xeoqySO%>GhbKHTl9tI(uX!!#|P@*l>H>gYlLJ`P;bdcZ*jJA`BYfZu3uN-f#=! zl8fJ!mcDE@kOhs(f z_>F__&STKglLBi9LkEde8^@g_c(z9Ht9xp$CC7dUO$3@#789;tWwOaAqv3xnfc+-= zUghE_h`8j|Xh>gRn-*Z3D<~s<^6D2j!iOU}Ii7LaQXsN2lULw-4pzwZySqe%$HfP$ zXedfH7b&)&<{WbZ2hFa8wO2Dm{zBdQ*m5te@S{&68scziBGzNN|Kk+N1C-~rn6O)bdkEDLElijlWu>1HwQyN9;CH!9<;PmvjWLA%dt=g!lExx6eS; z!B#=6WRY9C1D-*HpG(lP)_X2YY(vM)?3*;zZbwW14FlmLMDkLpD`<*X8KCkXhf#d5etLHS2lMIxq*&a7Z2Bi?1-$5 zPk-Q|6KV=r_z*RXu=EVmDQh)MYuRY{ZYz!k&$l5mVBLXGuJMd0KPfD}gcB&mJD~i* zN{d4D8K(X`=`&03f7X@5xAYD>fX`o}E4jKovPEclDH7ll_@YFg0wlc(fV5#`@DG zeJJt??@Q~rS+HA%md(CIKErG9LwEwX2C@+L#ZB0D9dr_QI&ewv_<45$V_f^IL&S)u z0oDX8k&^rDLK}$bq>qOWW;mzajOu>bdQ5Vq)!{260jo=Qvio459;_M8TzSe*&`ksG zZfe6|)kA+80N+8&E{qA0+&>}lX9*(pG`!;SpPYLk+#Q5lCiB~crmlMg-46dddj%DN zzzn{m@#2SVtrNV0PCJE{BxgX>qTSYbTd^IRa?HAT0~Km@bDmMy_HUBJmFhkWutAy2 z{v#F_w-h~fE{{Vjnn(b}Z;7;f;^Dsk=WqrzRf%8n06hWnGG8$L_oINb7j(RsUzH;+ z^?#sO?sc`mX3QJWtfN9yXD1Y>h+_r&9Aa=tC((NY9k8EjDl9!RM}*1w;lvfb_(F~XKd55?2~8<4qLS@dsD$H1_l zaWLWm4=z=+ur(!g5LafuAn?r{y*Wu4%%rlMkd12Q!PW1?@N8Gzam^}a0af|R+p4W(dM3AXo?>x@VmQk@KMwH+Z z2*Y!?g3)f4W5#MwSzyst{58~D^@9WuxN(JcO}NL42q4!F0E+Kx>Hn&Oo1>s3+~ z_%c?m0wq{pkY(uQWeTU{Z`17z9xEflZhIoGXqao4@L;I)BMqegc%Eta|D`;U-mO&N z@p)1k)1z->h3Z>}a+-QfBET}~ld6Ti5`z;&l9z~UC0RXdc40`D0E-S~bDN~LjM@kl z8_;)v@U8tv!UXp4TRd)=TXNtCCA7O9x0NRd-f1vvRvTFy_6SbN%u!`#329dz_ypYm zHHtIn*!88Rn-Mr$WfIduO3mGfDyNi8$)6Jpa2rbR*)%nN5}n zN2~%b7#X2>krFCwTtj zwP$=wIeuJGGLOezx)myNSRGxiGjTcJDJ2-3mnQ z9>G(|3n#j)L+0t>=-IEbcHak%SSR<&QeLMRDKamD6767X>ckIoHBIh&;JM3?Qu?Os z8ZY|iJ2SoFqdtx4WRYq7zw=aA^l29b>(f1M1gD;jTTA(EHt^R(+?Y>*z ziG2wbatbEh`%-^Z2$9<97Qw1fnx=PoP`|^&wwIEND!n3u?dzrRt!7q&&)9@0KP*XVuhq#Mr`Biv9hDY4t?9M0aaaOYs~ z7C89$Yd~6n)=10=u1(%!_P>>C_A|U~|EiresM5(@ygj^N3ozUUy#*S8w`H#;T`poG zU27D{s4QXO+b@(99eJFZ*3@B6vuzR+(}c?BHnn#RzjMJ7kSaeyM$q=>6-cwowZNU{ ze8CESh`;hxjj@=`b9U_$m~Q>M-wQsbH==7pC|zMB&^2SoWOv+5bfvPHC+wGyRqZ5P z5^7G5T7-BvRNkWgJx)jJmVcg-7Z ziXHd61A2u!>iegVZ?&sJ>yE*ohi7NOAd2yV+RrBHv(^UuWlW6}&CDxEuK}qFeOW}zSJQm^+W4IOv(9ZsvR(VMad%6k=X$Nl<3AJL zrT1vGk{0As(_5$rcS?G}D%o5#@bQL(g0829I>x%axuBGIHZ@{A{%B5K0T+e~+t?S) z9y$^eMgMVk3Z^0gjrr6(8Q(S0UqZm0BKDEJgw97mMpmG z0Fq~nZoZJd;Ah_M+0KO1SX_o^#A;2+WSn$ile|%oTtbf{!3=sp(>&vHL-*{^?Lc>F zcONIxQtk`0%C0K32wSzKp?@f7cwQGm1?!28vg)a_*m%thsV%pZC1b}N5hJhM!aP2fE zhnc6G^T!qlbpsBPt9}&zNq~Tl8JN8Iwt^#@lQ(&(3=wfUOQOAB3rPal>725K*U8F3sX+(;zr%kf{xP5Bd~HK5n*Qc#^(d*T8#h6d7!{zNJkp)WCv z8g#Z(i)}X$d>B;3SKpp*nSXb}TsPa7+__ubBkJpE1sc;qwirM*VOF z@w@IXRGgcVR<_X3ccjK+u|L_v+fqtf?Cv>F8QvydQoa_&s_s%jD*&mHNfB{|io>J)$KIb1`8uE&EqX*&M)hK7W)FvJV>kKBa}Qs!Nbm*8_+!m-S7=f^vnIc>?Q@pF{`CpG%aBl=!Q{jlRSg7 zfOLfiZUzalyr5?ybw)}HEiyw*sz#34UE$jLU+5+apI(7~w!9cG`)wBqN|x3Xp`wJY z`4$fEt!@8w4&5D%Cr-Y+(!x}Nw~zUHGlZ6Acan9cbqqD}wQoHC={H$;wkAS%W>XNZ zojA4EkMiA0R$6nQ1+*Bh@m9u=;vz)l2yl*f9jgAwJ#lenbW)rN8Au; zLU-08+geeDMkZdBOB_}N`3qKoW5t0%ihAE{fxMiT>(m3c{?aKpLW2lR&v?(GO-Bg1 z&0#cH6}_)o;4HV$c`23m&~r}~e_>{IX3gcTY)Vfsb`rKMdjHB5!6+jV)zgFVnLBO> zT*ou}DEF>&THI33cGZM0TY-Xp9M%^#3m_Az|G48R_A1JN0f0NQ!Cx6jJ$N7P3{U9} zV4ZxPJLDCgK0BmN*O-Ip!1>dvqu;$OK{VPP)VuyZ9%M{3tbkdCg(|5k%egr+97GiNQXx#(*W7^bEXbz_@s+7!}8z26PnwU{fBaC|=$a_8+Eso^ZTQwI?8HOfNWI z2T^*t#(fQum)!VpA^&}o^vR!z7%MmhB?x^}v1+{ep z!HyTu9o$gN%K48MMlh5g9wI~aEy>p6V1(`k1#PSCX-Zw6M3idQg`afUq}%URxwQU{ z9{)H5H3<>qjD{Zj2E&2Cxm)SbCO%M-yuZga@T~JY)M|danya` zdftnt)BELywOY|KO#z;ie8f+77xWxKnpv@TLBtK2I8eX zP{Mrs)jz_e;Lp`Rw_yGW)GW@37mkzgqB`dR3Y>hsB2wH3No-fU`{5I>;i-pE)MHb9 z;vea2auPaj5Si%58F0-V77c=TwnPAbHlmNqrdAh2W8jic&LpA6e|>j`Jb8yz$|9Jc zdEEQ+8cWB72nCupOs2+JXs=88R*d^;b0?Xg22+IVhu#w6E)`ahGM0t&z8HDs^&)ni zo9zH@LCk(*LmMV8-@0pjU}3-?_2?%p{rPC06I<7sI)Lt$WxSPjs~VXj7>h;1>AHBc*%0%D^Z0N`VH|_t^L-R6$L9|1U z#tD0C8bOV_y~}=AxN%sE}qJ&KvY|MU668flo(ojFe(lvr_5Y8vD=qR{$!P3 zsl*JmCatx0R_3`Jzq^?O0*HM;teuH&PcxcA;QCQTGOxd$yCY^9sK^YS;AL`-u#6l2 zYkNrqFRfFX_FDUDA}6s9@eOh_aw{)7?s!VXA3x+Mf3q%Xs2a9E$=N*t*mwnB?c1VT zR#KZWjwBLnQMUjq2{-YE@KVweWFx;9k=Fc#YowEd=>5%zi|KK|S;&9fm~f_X=I7Cg z&^q@}Iv4v7aW=es(4JMGPXbSO(L=)*|Ih6z$2DH<3@)jw0PoXF-;EUllj34mrd@6fH`q(46=)8bS6^j@`Y_ZS-VT1@rI55-9pH z%yA}sWQ3pqbzv}u{{nzhbZq8|q&}=r4rEJ0gjCISe!Z(Dz8bFF=d)5P*>}82q}Fe~ z7tCi-w2B5Y?znXYF8@B>DN5F7sEhpGrD4j#RPaKs;q&4{bp>pl z8x#|>rD1(ECtsMhWckl%JT6a-P!xoLfkKc!t>x`U=PXLC8qG2Wa~jZuTEXH=K?sXK zC5OJY(q-M_s9ow>YPq>-m1bHO46P(^1@@tqMn@%>6;Zbt4L zF4ZSJ{6XOs2WLv%NIcWXIkqycNOgCi#mu)?^@_UffRXgBy8XG=K?{bBk%3 zPGV+%+ne_Sfo!z~Q1FwY?b)lQ{k9FMfvE9B6`RV z+Z;v!fKlM39bdoqNP8SL9v0g@Yo^Zc6d=uIpsvs_IW(?*fOlCU`B%EG0&}LX@H3<*#1VX5gX$^ZU1lLGz zo(tCljA}d;wAR-JMB%lgrk=H}P4FyKDG5A*3%n*3YMj@y@#TyeT~~q^WOS>|L0}GL zwn_QDCyYx}!5t$=9pPF=O@4xKGd6ch*I1zd2_VR6QzkW$QiKMs^*ZG#nj2+v1g_X* zs>zlsimMOV)2CLc5H7UR;!J{Pox9xwGx$jNO48}Oz^Y+U2WASq+KYG+rGrl}YXkvd zhLy%M!c|gBSF=BIXcpFJtT1RSpTaFICQN-|t<-nQjZ2v9>c1|*`r30<-`cq!53DTWYSkZB*hQZirE8E>3K~ASpkhylY1*AGPUkTNqzPvaVoDcY z4%rQ>yQ=mdP<(~!N2idQ>9o2OjU+cx?ocrws;PNOY5DU}+T5&8G^d19zbRMN(;c2n z<~P7KvD->y=)+|EP|w)si`g+WzZs=(3=%zq&A75g9CJ(>YBj%{Ww&mp?hXOMnTZ-z zS(=(Pd+o3p%WA1&vdlv7omx|)_63ptEQktrzqBgnX=#+X6O(VoJc0cdc0vI35hcO; z?W>`>Pfcw-nY`A)?yi_61+uEliJ18ErM(Lek;k$UJKisq89KZ1y=eHn7M` zuc!H$mu7)=!;y#UX4D={20=I5Dr@a}9q&dPyi{@Q($9c=u?<;&m22kob04}GzKiKJ z(2Y6rgf>=W8X9n_L=DqkI*#%V+jF2-@Zc~_i~22k-ZC9OpA_=VJzq!<42|Ctww8z# zE%YvvK;DhoSurG`2Aq;T2ffu#ARcG|5e~q=3?*3J1+|$E8s-I;XE_9ynI6W))ZC74 z;rl(71^_-RsDOuQ-j6J@H@*gU-u1MNSU;((E*CCf6uF&sfqtS&*@#1+^gZ123V5qC z+ldC;#NwUy)e)}6(#Tklf$$DS-*M4jB2c#ff#7iiBeL?>3~0Y_~DG*u)xrg-H55vP3YHyMT{2 z#jX`$$bTeYgg}_dax+l6E$wy+ms84%m&TcX(rSX6ca$T%(odbdR|Kp!z|+Lt^^Y1R zif;IDO>yWaDVi!A3_DIptGC=Js+8OqI}vqqAF2T_VTSbxaBPOnhs|Qc1RBQgXRJ0_ zP_-*IC1!_7}^iq!o;)0fH}T}Hf*KHxM7_4vNmgXs5w z4m-H0vTa6jD8K#XtLD(aMc|UTp!c!^U}mw{u-s|K60{1uAB(Hw3>O*1<)ozcknO3* zGqN}-)GD6V2K<6<`8IR!rumaoV2{T8*nQ{pXJ5UhtS&$2AB8Zpx-#&85Sr0!d!frw z)cz%5@)o?sun)Z{i}yGSs$T>kbtw$P*6#Cq9JRzANytK-l8c~w4Q ztYOF3g!U22St);e6ylYkG#3W06Glj}X@m`D$1yI)Y0n4kdJ-u1;)=_9%k)reEY`T7 zTB;9uwyUUUm1DL|K0dW@LnJ4AeL}v}psv6tN+DsvJotL)eYxst3xP_;BolpMFm`yEYS&jW7bhYLSUwv-cq-{m)$qlL@?Vo_09gC8%p zem-O|07=+`>i~Luw*~^B0gikLv$`CIK53YMttW-)Ds~KArIJzV%LOK@DUKy&Pp7$tHRS z;MdQIHoEkVxj~RqVK*mspad25A1^t{?lMl-e9>#}T+lokC_{T>O!M5RNI-)NE;wyNh&mgZa!C(z5ob=#3(aJ@|z%wUe zZ+K=|fznClKMH(laM5-QMAUMsB~CYDPBtXNu4!2cFIaHyaLk$y9`;&>scVpicD%?J zgmq>6uyS>536V};eZqUBjOxttxq+W-B?id) zea`kqV)-W&`e_l>Cw24(rb~Cg?FbiK^4%h*_f*v*bt=Gh@6ubQ^!jbd&~{G9Eb=#} z0Z9x#wR(jnza5TU=u3IB>GF2x+{ui0R+qM_G2*lOt%MOd*HUe_M6ytsFvtUIFG#+D z%5guIPae8eK8l%k4zZlae--s^psC$2hdEbJTQ!_-Ch-!v((CgrIxY@gcislUGO!TXXb`aGum>g4 zT{u4n2W+k@ht057tR|CSMGGR`^P;Z(Hj^uFbGREL+Yz)KFsBvYQVG^4IN&AzKSi1@ z@^MT`+It#?=acP3%TaB@5&!gnblDrQfTK_uJuFC|c~~tzv%^U#gr!9a2sl}%E^}c=+Ud@feY{@guNzpiGv*aKdx$*itN0-O^SK@`JYiHbk28B~Wa|GC`GN@e$ARuR z^5c<1m;dFgKV+i3Z9!xBg*8A?s+;tb2hKiWWWH_l*@Mzb`0Is!r<@?JM4NwhwNcti)O-5oVt61$u0(>4N8<*$RzQn3`U?5pIzFnzw4 zIu#s(U%8cT6_nux_KNsv+l>Uu)-U%1q?_BsAM%nJ))#{`|(+xZ_HrTUcRAVWC2s*}$lLfE=1xSK7k zrIKlNG7@H#rOnkwKcJfzGdS1F|5H!l?ME`(ifI1V9b^MUgr!zvPTd<-Mi8G`MetP~ zTp7we0BvN`&Y%|C~_caZb*=&R%lB=U;J zs(e}u0kNHADL2B%dT19wbG9gl`T1`PuK;najGJR*hBauZA&9YvEmdlcALx`|Y?#=^ zv=b{9lZm*9@(s$u;A*CrC*f!rr26xqyxK9~L3Q-AasISb3bQftni zPBDxvYbB~jS+dlELuIkHho1Yf;nta`ri~ooZ=@$+qY{?wpi)fVaogiq=hcO|mJ+5; zg05eXq~q`3=;Yn@*WJ)(eL(UeFkZ6pH693CJ6n>PO*X2O^8ZBm~g89p|xBSwmBf53EAuDXoK3K#xG8*IfI;VO)%Qq82zJ13! zfCDX_ec|cQ^~(WkD8X&MA%H=b>|cEM$3nLX{ck7;VbF0g8WyakRH*5U<|1MsA(V}u34^Y zrNO{tf_86)U>~k~y^nHxY6)FrVc0HEZ{qm(zP4MUBwd?r>Ra#MxBNHfrMW5@e&5Xr zHEVz!{m_yVAV&h3XEr6^-Q18zG|R)Fi-UZ?j6*Z3bpI%VkGLr+{}D1{eiE}-ht0gj zw`6LRTLyJv6I9KeBnUW}`-NUl6n*M932_dQa>#Np&HUJyT%=e9+Qo_U&0o>okZkD~ zar6hKcTJcftOOemaCd?qUPE^zp(=X5Jv&j!ve3wr2*kgf6{)mIeenog#e? zjsYv_V_slUs1rm?fa}ET`Vqz=n?Ds<4-$>1th|#)e2b^{g5MXY(3fEKW+=d6B;cG| z`!i@^_JgbnwIqeTRUft;?oxkAgnAvY@1zR8;AG=Q zK!SJVCHk*CmfI%~W8efQhG?Rq1<*B+^{{0+Z~7b|C`f~N(l{N)2=z56;7^_DM-7YtiIPyz_I_U0;p zBo+!3y%dD(=;S&3q-2St+*^v zjI@C4K>$%%ACPmExmKvDKHvqvRA1AAL&!7*Pxxt?bEY?Y=2f^ur^R&**Q^DKAHH5e zzUQ(v)DI$&zU3gO2Om?~R5GfAG3ymoAdnrvxiiXIgPfN?Qg!qs)ZB759laKcj)=B2 zW1CUsQWJM%#58rFR*oOwz%ur?TM0~r97nFTABJ`7oR1}h$O1s>2%t*&4BLr|Cugq? z$Dt*!0%52!0OIaRFXd>Zq-F(pguHGhfA#4P@e@6(YV2WMKa!G4^t*SrCvbhrU-*u| zTVKUmzNg&09(0dyrvutzRP^iZLh@dM4Kk7wPNNJ?pwuudeIbFy&`d*7vyOGV^^m1J zrfyL1?HSBe8lyoc->{@YDw`9J@MP~)!4hV~EadiQd_uZe;9~cD4+TRV{fGcFXVfWv zr^J&mhjY3AgsTg7O;hZX+-n#NSBo51^F1$u%5xN&%jjFn%bV$DB8Y5~*Y0*NK9w_N zcPb~ps#T}fh{;O4nUtc6|LxCp+L*D(CmhJz2ADjpzQ)j_2bfNK)*@_@{()C6ulshV zB`Ml0{F7@&MVoMA>ASg4l%^I^Y^>Z@f{Fkn6+jG_h|$@#q;HMmzT!b?URVq=OxO@P zI{u`RQVrLGm+`b1(Y!^^YuZx8ut;{!%AC=`XYRgmN^g@B{vD;DFheVKKzgpG#1L9f zFlBn;kYSP|Q|}b*$Sj&6zXAjN1=gaU<~Sa63_RN=D?u9Gmw9{2B1c2t9 zn)zt@4NhiYy7Cuq(gBDfAZE9f2fm&trc#pAaIO)hQtE3VY@Uj;90!xCgg{_thxyr# z%)uV?cBT!3$3>X}U&AOK%KQBOPWU12XwXG266_{3l>Gdv#w;vnO#n48AwHt|m=HV_ z3&vfCUf9VyGYW8LXt|WN9#ya;$;2-^*u3zcIH8P1nG&D01#3>IDmZ#gP#zqjdX5*R8 zkMk`C&&auCr%q35{M#9d`il=|v6V2eWD~Dk?RVW$A$nLbQV$;rLRb)-W#jo7#KeFU zYgi!hlIV|n4SLHAYb&w!=SAIJZdta4&0AKUiUtK&lU@wZKy*FFgGH}gL`VXK7J$48 z!h}sTq&oEh`8e2MIj?`gSbg%O7AlE$p5I zfdAMsA6}2E44;AHnCD|L0=gdbWUD#pD6j*Mriijv5Z1J3S8eeP%_twc0D9o??CP0( z;%%k<`|v*c_H=%`s#V+>}8jr;*h0U7dK}~dn`UBD_G0{*Th-r@h4IT$TOh%TINa$ zeBB5Gj3a~?ga>me!jR1oyZ)IvtThy~I%#Z~Odx++TS1jqOnM%IzV8A{YN25|+h z!|cSzzRr1EnZbt`)D>%X5FM(;n~LzMvJ$bz1n4Hs`+q&9Dez)Dd(Wx1dO^vIk2gR6 zFichf2(8#EBnygTu$4t#@!45L|5n!w8#3VR#Ke8xtuyk#AQEF$?I6(Kws4L_Z(QBQ znZ#X2OlXbvI`|9OLg)St*1%nHZf>azntwfyB<634_swd%SPs8%eU4!a4i@F|i6ha+ z+xml7Paa0O0yM#+-}`kSU{t5`hf$kQn$M2ITdzbw!A#U@WF-smF6S@-e80m`is_&pFH%AxiWQS(4+R0TG3kvllg>cwP=w@Qa- zVXi6t{NF|ts7dbw*rp)P5xQ$|=U+e?hq?~_y3E5{g%PwB30G;xpWBr+8Up1!g7opV_6+mZ1)2=hZ{*HO0N%P0+H%yC^|gf&gs#2E^8^bIRvkIgeT3x0 zIHKf?L}cufBv1W9#3H>^undOL8AvXKg|BN+F$cZkvS^~JJ3}Im#R){iF>_mEwd@RUK;&C{aQk601&xv*&d4d&3juuNJ_$(c|Mo!e-Xr@RMhV6+U z&vJ;QQ462BiE0RU!g#Ti(RPydW3k7rqhTYzB6B_^11MQg$R`}pHl`o=F{%BtPS36m zQqyx_nv!llx1@K@6eMh?!%vQZUf{lJ4)!(eTU~ecMnefgNYV~)T=XOJ$lV08kGs~c zh*8oCEbg_+wh}MzWSwb+a*wNU*1Sv{4?SBWFz~KEJEvGquep#hHNN+EOF(eyFz*0^ zEkS>i3ky`lCXenUKMP)*yOjlX2jEw|`e9|Vtx6kaI^$VT>ogIze+aQVVSR+H^(QP3 zg!3!p8Hy5m8qtxw6#T*XwegEtTyP^uEP3rQ7eJUdT1bTTEZc}8EuXr>OV>+F)4*#{ z=8px*qKm=(D<+D}P&Ne>=ei>ju}}h5oo((I=72!iRXqhpo$KJ&Mh-5)!Gj)?Bi3Rj~_wl}p1SAnJxy&47FSx@ZT5NAY zAH`*>Pt6fOe{-b0zRJJB;pid3XIi_&fwDX5R_O3O<=6dOEF7J?4ksl6wC~b3^jzst z^1jz{{_s8}MMvjd16VCz$W|GTDe{vx(FqJ|r!JVCMSB`NpZ8A28>V=+io~QBVePte6n(?W$<{1}RnaIuo{s|ac+V=d zY}+camd5ZS_?)OL`RmzDq~{W?2Yg3}S?eL5XXh$v$gkDB>upz|wO}G(b2%SkUA-`) z%XP>HYP;R%7Z@0S;q4LXT3tXogdi$y=^XYn}uiY#<@YpW#kasK( z5o1*L0GE}pz0I5I*c>n{6oR z;|Y5Sdh^BZz`z}$TPU=Th{j`*N4e21{|!UK@2;u7T@2mvX^&9BB^3s`QG${OQv_yO zy-X#1*L!ZiIWM($3VZrg6UZ_pQJ~aP2IIgC^O7H&l_f8(&nzY7-E=qJI&HJi(!vg^ zOGrG{2o!E<?_CDbFzxSMmHYL=$Azd3AVof;vqkY-9fmLaY#u=$|5vj z4wW+*=W`Gt?nX39tO7RWr!Zzhc}-tgtVzgmNZ)db&V!ON9NLYTRgd~Dm+x{|LDMdN zL)MH0XmRDsW;6=*<#efMf$$lF60>nw=>2F%mp*+ysPxWe)Z#1n07Ocb@eFibzyJCZ z_NIrVe|xe?`H;)H;PU4<$UOe+3&hujN;i&jH{~?l1!YN5nt_7les*6;M*jhY170T~X|9 zQAn|_u(+n8%HsTeiu}B%6A=0JoXyW4K`M*uHC(7zxN^9>(07px+V zZ(>HWItonX!_rnUy4Citt?PrVQL)z7egh<0KOO_e%Q@wH52jF@vEF2Esn=KIg@tOG zl?Ov=x8+tseSij5EJSD`uuP^e!PaGs0430%!+r<{dy2>=>1ex zo;$Y5CTH4l;|H?AYuY$Y=}Rd%B=yFlkrCn;k?kH)iAK(9$H0oHZdnKt-g8~zObd?8G#<@s zVfQr-I>OG6!Z8+HN#A-ITGq$D1zl##u++tr)lYa(bIXJq61>vs1Bunx9wBrg@t34s zPZ4TX?xT3=St|%SZd0+|F_Br+u)uerigjU)KBSj;rsSapzft?n(t6?H8}Xo7+5ABq8h;f-Jmd!Wl*euaM5}c#Rxq4pKkz% z%?{bT^Jj${6p-o`iZ^7P^~BH!8Y28 zAcJ8%0@A8uD?h~LZlF1z%>(Ozh#sr95+yb7#AWwLg&Cj8W?g9|BrLl^8b_|T zIjQL$aQl5{>)0Idyghh4^Jn%hOOh4*{FE4yM#B{C)-leosh_WEv4(Vs2tVGm23%I& z)wnVQHz~BG@>Y_tNZnqjCHbsfFl#`j5E$yCxE%IuEzHrJ+HDwnLT8s8FLw0cSD!Ch zr-Vp1Vx34MAQhpL<`KCRDxq!QH5YEQ;&f#y-@1aTChckL_E(&FZGC_CV5payLG+2P z7>mjh`9&^J0q4$^Qf^o!4}?7SaK9f_m$`^Skl4ujrT}}6MVN?>hli_2-$oMi6Wy%! zIiogk>?v+ptZopKrvi+&8I3*YBYEi)U=BO(3I-+>Kg~F_!198wBnRA9vH3PwLynRz2M*P4$U6jE7?Ot?_x z$2(CsTN?C}e;R*>2?7R@Q!&_H$;Ray9Zd+B2YLxm>jLs`dw-JqB`dgaT!$PuFtM3Q z=Cdt&HUE5)L8MjhCNb^r6H@N>E{ipfk3>*!JUCZoC3+MS#Xa=b?<$0~W)3DB^IbYAEg zb2Gr$!0LMfzWm4Dw9*Ek=|l7by_(G#hpHRDETpLtTcWR>$y}ww2jgiH+}lx4cO5+L zhB~tw=AUPJpPIatJN^g8VE0UlV@rwca$N7jF_ysFLb*#efQzO+$V}U_a#sxnL4(ca zj(+N;VfHU``}&xKjW8J4m^s4>cEHDey6I^p^t;~PST&Agpyc_GN{kp6i}FTm?3j0r zvHH!g-8(ZOf7jXE9|Lc&kny}`U0rGvgrgV+lq$oukYZD}N`70YHN{z~ID4AVWHp0< z@Xn-3e(*KyuZ4LoC0PNMAVjl3}rz4R+^wLXiDB|`}+E~ga&agGjHa`{l zoo+Sm!g2hklNMfyC=d~SxkHe1w$we`{Zn(tqUYmvX@)7fvn+lWr}3eEwMUPN7o&UL zAy1p0cjq~r2~GMA`1RKW905?%Gpyb%D4w=Q2=4hOeNHf4Ia;YWqIs#H>)vO5`JjCFamjoQZHRKD+-YOA*@>*HL85_| zCY<;rHht|C-x6E-e6z40t4oqSNZ(Y?Oe2(=o<_-uk*iSGDi9ij@PHSo&PnVB?-mZt zlV~gSmh2fC_#eeaisbX)?3*0DTrR9nN7~W3;T5Kyop|lHcc8riW%LVHgKLrn2XIbyhqun|sPv)|6~38&D@lOhT+XPC&+&uNbCT zeJ%EIw5Tegh!~rXgt*XH1AL30@-4?rApkxV)Ol{LZwo8-W`sa5k+Dtbva7w1ZzS_S z{)fcc!g{4H^htT{!2X2U<@xwKie45`%XpC{$DF}Og@6k`?J#j)>^!Z9ZJU3H7y5nj z+7O^Oxa)~m>Q8tL9RDGh&-WXP)Q2cu1tu24;fYlHK{gncoTOEr{_52q-);Xa<;!t&PZhE0#Bn)4P1t8}D2zd124-R&B`MjkB zl}?v&+t?sM(Bi*@b2sHhTB=F++psarJgev)Jb`8Ze8zvo{A;GCozSw4l^Nm?)pE_6 zp2%0*pi{q=vmLz?@cOWWE0g~!s_sX_bD8`%GyN4RU#~HrYkXn=vzX=u-s>{1Oq_}Y zrVoEs?L`bw4Fi&qAL2SWo85!4IK^QRGDWRI^wQKhjSJ^N+za1!umJML z3fIaza6!u9SH05bLcMZFPUK0Nq8(D81XTA%%b`BQ>kp?rY?L^1OiwTT1nvq9E<51gI$8HetNlsW|lN2>O4f8 zj??ltEtvG^97lZ_eCiSG-HBQ*y{RAiBcgEx-7x&N*Wz5QC>e1StU3pV zp5+m6b-B(1s8ws7X6Nu6MZLITW@mvL)$y#B1A7@g_G7dy|Ltirwu@W(^N`yP+dgN+ zFM!HeZ8FadU}rU7x{Sukm3k3Zcu_CD8$C6f;=Fof_#R-Z(J?%&F^f_>0V@0Z53Q@j ztD-x88&lY6ZCGRtI!esG7134B8t3)#r}JcdWt{E1nXT`tT4d)hBch)K)-{h`GKDFd z#Dp288C7!~1;zLipbmXxMQQ6=n*0sn%8E7AJ*kQdaY3?x*)?j>;`=z(E!6ST+q}+H zinC1MjJWXhX1m;z$!Gp)cH&jYQ|#xvb2Q}D4@+~nWr|gHP{ONb@}ABraWy5lJA2zR z7Ecvv#M6(`cig5Z>R?=4lcuJMCNZZBlj64m)cfi!#PHp4)1VP2XBkL|fo0k${@U4Q z6hUT}6U*NUl#bP74!oxv4(0w8cueLJ<@d6duYt^(aN`1wW`O2kPVAsfBfC=WzK$?8 z!!AXI!G1C@2)62=4*J3-217Tgy`<+KL#w{8$WN7PL1__2kdUNlvz3gY=7E3qTM(W# zwN`0C#c#U$VbO6Bxc6g)2JkVyz77RkfJp|qdA~tk$5riT3*MeVTS2rCj?~);9PI{> zjphI?XR8zJrIPRh9ev+p#<>ZWDu9$iXizC#ccsTPcq{X;Tc=&|vT5}a`5U07hE(9` zF~l-OXOIG~E1+%6qORDZ@AS*_dW9k2K%Pew?NM+|Mjzq^mqgi*eTj+cYAP_oU18Nw z0=_j*pP0e5DjSg(mHQ00cl>EOU)d$jN>OvPlSs3R>ZvDC4v?(kp$AXW$6S27|EDThu!gahO+q#Hqm0Hiza+mumM- z*mX#*>&Bf(3CHL+l7LQu;-I!Iq-a1|7Ur-><=7ozBbQ>7)n*v8-7@>UG($F7O4ReQ zlKvX*D8XAmIO;>?-i<5LOD6MyOk$avq$}Y=*WQ#jBQBktb9O;D3!3aoE;c=9uSYFi(acE?wW!n4T8DL6sRQVy-(4e1@O+ zYMv}$Sx;p0+Dqw69Nfsg@I6!Mx#L$h9o8E{=7DbFihG_uUXJ)HPs1`-<7WIn**Cv} z$=$jJSl0ypPjpw5bhuIx_X^|$h?3E0;xzh4c>w;v<*06pn3+HT+_2g?LHbvDF^GAf zAgusg|fSgnaI2s893oE9@aY|RksbU`0u7H+b&@Ht9*68!tCd~qB zB+~1R&xFJvi5dVq5Z4%xhO^?*Qk&<5oU=Ljj<(Zs*n`_(I zB2+IhwHG6J)9!0|kwcE$L1fNzICwFw0d2$%BI}x$z~%vG?^sgW)tu5>cvbAt_N7Ji zF+@vh1=Ar~2ZS20Vx`WG6y^s+XfNNsStK7V(pU09wVoi(q9CIO?mZ`7u&y)*;;#up zBnyhzK>eX7?;SK6rAmyliy$U(Y8OHq_KLI6(gF}gH0!7hE&J`XCn)UFB&3dV&1B!nxg?T{^=u_!Oostd(l z7upDRvbMpU1@`!|^Y37)azctDm9Db`s~}^{NA@r<7!-DitB*GW_GbBt(X&WWy|Rg_&{Io$|heitr%NcGApn1^|-lIr5I@Ktry zVw_)KGP_xcAVDn^IFg}v-T6d1puNINp>LWpSkY4M{LG=DCf2Pw%aBnNJ#Ht@1df|whGTjH) zc{#NZX;AVS7SM$jVEt$WrAPxkDbh()k{2jALWq+1eHse~{eH{6V!~B9YKcn%aruuW z-^$+TUJK36Vxk>hC9~w!!|1+qW7>K4nv)vZ(`(BU1yX8rOZl{M?)Ath)VYH{SevA| z-A>QAl?48lM7B0=vhDN`mzIGvoiBjule`M^c^vx&H>2cKu4xKn8KXULfo+q@eHD3< zyU-CvvSP=RECqpRZIbz9qkNv>i!b6Xf?%V zhz}Y3$rjWD?xx5Dm%{B!LiE$ql#PybwklLT zuzukbZbfV^Lq?gZ_C_CM=(|pJ3CopXTfDB|FL@i793@f^ngL&_Ev?mXM{YGF@*W$u zhrOa~?$V1JeKkY+q)`}+ArRjb3-XJjW8HcQrG1xkfYA-+q+cS5=`GKy*vIrnYyS7e zaTmwKW|qRj2jKx?WTgo*L$ijN_etpn`(e_h(*4_8L0F<)NG1#@MTTx(a*r^rVVwJd+c!!&?xCX)Cik^p`R?avaPjeMdM$T*Vte&H+CztDIf(@W z89XnokVTvbgCoT70GuBNa!SuJ>eArN#6w=uqB;5cZzvU9)kRnLyu!p95lMc{+XrwS z1D%@ZgKj#+k9{>yT8-=l72q%QonnK~JK;M}?X{@AL9tevM_H1SO7mxYg)wbey>r8R ztQo9jJ*SQX@%|f}>o$I3h1gCt?sJq)Q{!DlU)TgDhtJp&Yvg2qdu=mF7n^$O1LuWb z8DztI;YF>i8S=~eGfN=lO_%qGG$j{0ogY&z`Hb-b)c00uUiiF3uuCDlFX9+hN}X(L z_@cY-r@eWb!=$zyS#ZY$xa#t2wjl#gm&wZ0G0GW(mTi2&f@Feh2XZ!n#J8x4;ix z#l!l2P>0%Z<@Hfyd2&~;LMfompPISf{U(A!>2hkXAX?@gP~ul(pv^Fl-g^AmQkOlCmbW$28?0)riz>5$@phFf}IhKXE zwU*yu!7FkOeVQ91$oh;Xs^E~?TezMJ1yzjojc~0duy2!-W4-nHCIP=%Fws)08eY?`>9 z7V5Su!53Txsh;I{7j%>LIW00uacO3v=E)RIq0GY;3xX@z7d)uVJ+?@nzUek&zv@=F zMU#0O`Xruk!hGofS(c7I)&Ekq4GCDHRwX6}O(yxX0A7lbg|!9q7ZHg=K?q!JetoUC zHYDE->Em~d1;v!NXCLx`6zlArCmYT%ym0PMPh}@Fv_B!dNl!N{rFm!xhp?Ox&t!`N zJV3Fpuklgc2o4QYXU@VXMP2k?j;^Pzdf@W*JjS8!_zHa%i($snC}JWLU~mm{u>xD+ z5gE%lDClIF6Z1!+j*y!? zM3_o;lZSbIDyu9uYXHh22Li=Ss0;_*@#=Z{feBVHC4M-Mp*yqSlO(QbCf8M>6bXu| z_NNj8&#S`5Z2vi-dar%|Kp%lECs0vf7a(A}Ajti1QJQo^GvoN2g#AZ9jjN=9OKXQ* zK<_klUp}7RNV(0!!m3Kwvmk+}REgx-_E%@hLUW&4o-5SaC7wb&kOJM*_dP%l-=k4+ z8@DS7wLyu$S8Dw7uO!ddHc;3E&J7in1QaW8^tD22&HHS?hgWLCQzR!k)Pfyy zBmYZt)hJ2*<~FyU68Y#8n-3F3WYe}!&d;HuMMH>=SN!r zi3}O;SndKI?RC@S3sRhQ<-dif3$~M3a^zEASDLec@c|WZ?bBv4SVy!!C`cWeYMBSL)U%&j65|ZXTz52%#)#3!r zxHY<27g_UZB?d$;wz9BO@LKi+*WIqU?&7o(V@N>ijH##PW&PmU-ed&7RBFcphJes3Ta7ou$D~;U5~(%_U89#NL#P# z>^*4w19Yh)i~`x0)ls&+$A!o<03N^GOBb*U`ZlJiGW5Hc+A%Wwb?b<1(7?c-w)S0q z4Ab91SB!&uT;X}_E~_NrK0{aE$M3#AcPfqSB@>CEgOHjl5}gF!o{&tLQlaY8HuM>x zHL3$MpQVBa1K#_2^S4aOb0T|2u9&(xx}8B-;ET|`w(_%)$(pyV+0c2--4OCi`;FQEuqnP=~LQ7z{T`OXr};|>q&}eL~H~1OjhnN zdW9ImLP9%>t16G2RCV!xQyhMvM*N*WO(|lO!b5N?fUI|l8y?@q6)oQUs7%%s=5rNEQ_qmkI8M? zEexLBh2@QHHO})V*@uE2B(xBMZLFoS)r=@kNH9-HZsN2KQzSHeTR?cs2he~>brJ0w z>{Tx7ds3@(g7$B8k2b2kEC4d?-up@^QuB)cdRK+fEm9-g%_vE`9AW^CT>LY z(W-b5suV)Hm+r{{HviR?*$y2Zc)KMl$MH2cr=^kb&01Je20BagH3|w@5BZikGcG0! z7;&&TdvUFKsWVnNc>uLVi)bOq2^$ULt|$q7W436gWz9}xE9^vazMT@`-8tn2%YZy? z%1F88kKsFkFcoeB9cWQT%=NWZu-}8ef5WRI0F@9cgvs0ux3 z`iUxZXPQDTC%>C)0F~LYi7c{gx_2KY5$QqIF$hI=S`X}ahYQg81G*ohMB`TT1RI`} z*LjUOe6zCHHqeVUM5E{@5fBLA?T;kA3IvLH=Q-?u4ihUt>?D?_T-~1_MBDg4&NG%i zv<|c5afl!8iA;R*#)`-fWRYYID0kowsd&Sukh@_GlBF63NO>Kc_ZD#35As4rN9K6C zpP+1?3&UG=pfXfR>Z7@ab>Kw?)9hS?K7m_&2Mo&~&GSy8k|101Jt|($mm@+1Br{c~ z_&Ova;}LyBi54GprW2nhgtX%2?9H_Eg$pNr$4eAzpdLmYCDv#9L7fJ#bO{T=8Kb@) z52{9nKRL|sDE6=ZaK3End{fRKN4IKy`ZB;d9e4*N_b!4xu1;f=J5eY>&n*_kVJy(V zjiC@21h09=Z1_7aCV$eeX4g+ej8RKf^v#8%up-P41UF$e>VfVt zzE}>*ycg+GFJ)KZFq1mq+E*zaY##l+?eJtZ8>P`-uHb{P_J|C=8WtJX9htyP9u&M* z=yt;JzOwy^8~e9^tRdu-%vmtG&(BeHNf)C=ehy?NS}V${2dW^8_HZS+Y<`a^%bxU! z4trQVon6n_p3gxoX!dZ&$)1HV>%_1>VB8;F$~3372FgC3WLp7GkjqYz^L@qt6RwdS ztDRda89z{5dru)hp%+pnHo_+pdhKd4Iokye1J4vC`p{E=-^^}*^A13%ek!0(n2)>6tDj$rpfp}^f8>umf(dnEdPe7M&{ z6O{hKfawfbc)qVq1ursxfIYO-npsi7(*}f5f==YQgHFWn4w6>_#P@X>XdNoyE<|~(kTuYo3@+Mr@`_->Yhc0PTW8vNe(oUtFB8|Zvl#9LO;xs;}NE4^ka|KZ2<6&crryhyZU;4<$I(Q;bBq_@kl$ufHMR4QlNVdjG zC&>UK#KVLejqwG!+t70@{)1wi3q#zzjse}3y+@!vgw)xid#qd7XCBLDiPR1_U9$tC zY>d5*dkrX|9;BUmYKN6ffZylb`=NkI!MP%HzvC|;%!hj{&K%D0N$zgU{TovMZk#>D zUk7g?FpFy5+!v_v$zzoyFv3UybYC%lniFG=_+C#-6a|#KHovego^#JU@ zwm|c$FjLo(N+-qN%@t0NJyzAicWibT9f0p3epQbmF2vRSUWPFjIpBd#0C73lKzR~8 z!R7-Pw`I&n?K{F>+|FbAl$`s=a8fpWcPe7E<1ry_Ond`jsdN+!07AGqP7hO2VYUtU zI%+I^viXqaGFdi`qcB#)w;o8-`7l{+cCV)1RYO9;1$9E=|JrUhHDqZIc=iv_o0JrO z%opf28^#oL3Yt$$<^&>x<766_%(u%yKHh=l`TIT5bqYy-yA;6B(Z@O3JE`=`{`?y6 z#LCb~tSti&Axml-BT~BAbJcR==Dnm+C_#Jz`$EDZ1lBNJ5h5C77P5ix>^g((w70b*1r3^b7O(96 zkzg}!8vMqQKF#_k%JQH_FnHMUc;r>?>3yQSZF?U#yP)Nu%5QaPb+CLs&4V&Y(0&+6nkK1NwQX)-tk=c;tp0vWlZ(WR$@3hL z(?;wRDQ#ty+dD6I`WKIY4@tx+h&OJL9cpkDR*K?$Ag*ELo%U_D)Al#>vbx3ZhyE^2 z9Nh^HTciHp1E5I?+kD|3_DLpSRK@1HTj6;=($VoHZkiX_uPp!%=M|a|H~8Qk&P*y^ zfpKPK1CV7mV9|*~lUAHz?&5ZwEG$tnH(>vt)-&O$+ zMv>8m>GF`zk&{-(^>uDiKy#!Zk}j}_lRn2FhT{owqAN9%X>n)A{Cli^#rRzcud->; zNAKMjq?*PQu!}L6j|J;F2Cyc$oKuQ(&dUVL8}U8fTs(oSPp&7Grr%!oxWjb9T&mzD zswohsm{D%AHT!#@u3+rw#VRiFJ!8b{co%Kyq8&AAX}r`BKDmDO=mV^}(*Xd~g7+XT zopqTxEc#J|8WoH)k5&a>5R?S$*-`H5lT%!n&W206%0cLL|xt!h#b_dO1*LU_1Ud zm3lnj7tjjJgCG-e3gZC}Y^-R{XrG89Nb5|(J!%?>3}xYQWP46U3Z?EgCTr_l^Z62# zaHY6lfuKY>pS2wcFkaA$hWmx=uvWeUnRs1{d6(kNp5wu1B!+j6!1*=s+MU}!4#}^% zAH#v-=q>VbmHZ+4d-}SpVjcn=H4^&i$2E z6+lg;^NJGJi8Ky6oL9CnDu(w>D%a z0_MDCE$$6S@Ysuv#d0$BMD8-zXZG;T46-VQM30q*C_6^&7pay}6d?D7WC72M zpo--(3du0f2zQb6WhHb3&ZM&@WnJB6$BC9Lj{R*F*c~0|Bvp5nW3AU0n}+c9GLTLF zWJGPbbD+@lMXD)|rctiH1y8xAx~@yBwJ=<~V3g#IM0IpvOb(mj;zPJY6>+ap%Ea7I z7H`jypmrA^va@fUWXM(ahv5FROQ;LDp(H1vx%{wm>2S|`aY53!&LHghT`-AY|8mfD z)SLc1A<9$f0Hsn^598Xu(w!@_jO-3VQG0luT;w4BgI=8IM9Z1N z4^}JhNFnt9A@O^ptCFUzcH2fxWVO2K)A#wk@##!vn|M|%@ybp%_pZT=nGpF_dJ1$? zRjp|R+O=5tl9Ys^R=hfC_Qf8t|E9Ikh6;e3TBkMB1e)9;?yPmq=*asHc}kP4vc3$~ zVi~pYy*QdE31hOMo+B8aMyu8g)_aG@-NvkT0*&k#OuXbg_DsenOmv_0oLb#Did)`w zM(?8?TaXa5tnpCbrhFDQPzK`&a480&IhiZXz;9J&ktxBIY@)0DjTnrIHX)qCSa0)= zs<&(^-Vn~@ze!KS2ZaU&-fG$jf+Dn8|v-zlKS=m_+l=@c~B@#PYt!ggVt)GQBbEYKSdt{(UFA##ALisy^SAf z;4jwI?>X&|O`&QFjPf6Ygb7B7i{bY>+I=vl57bxtg<9t?YGpN`7*JOv4!EMb~+ z^lE~SK`n7JQ|Vlh99-2A9i|5`?$lW#C;3J}W_!+XmB*8cea{1z`jfoa^5|vO!l+L2 zmRV1U#egE%m*RjqFC^o5Lqr0R6&BwVQb36B%(mDZVTt5Xwq=+KNF!X%uXK*$5Vkj` zjX_KekR`HFFyyU*&Boo0m+I1Nj;K)XBp@wn+Ux3hqC1t<1R^$Tup^vO51Z8Y zg35CAvd@T$AR~?9{JTse#5+jg!{}x)H_aA=iEH$2r0!W0>&nGW>c#g>W!LfR^EYjH z@^|{9E7n+b$L2{t0xdzc>Xuu8dk zL$&BFl4v4Y-bBWFi$R1>8J?9iZ{F4LYcDvz_xJ$kb0*Q=vQ3Dx20u>a9RlV}c1 z3}>U+-J_MHfV=MTpPXC<{+waBOn26QM6}`&EPZ=zJpS}Ags+|;kD1#>e~VeWy{TyG zg~UJ@rUJu&Y%*z%va!Sqyq3i@y>>9f+{INkP=8auM|EQ==BHCoVModERFbx z;Ax#eYtbW{lurwA`ZJo)f8yk7KW#GTq)QBm3N5Tf&xsY-((`kGT4auxk`Yv6$1^*w z`?tXr-OIJy%W$jL&!FH~Utnti?-+d`iVwgQWyPysaWEj0_^8-#wQB zT^Ke@D)Gkf6_DBF68G@TExho{TjV#bkv5Lj+~dF>KMe*}`a`9G(&!sn`*hVU>lYt&-=LJNfMVI87`IKG1Z5?M26ORkORW*)+-y|| z>FHK4ih&iZ>>d@5xl~)ID~ndGq$a|YW%sOLRwRV0gte+ra`5_6<&BxE&;i%$OBbJ- zmcOn#Gp@sUo8?5HQtf*RmyTX=HUYk&L6Zg=sP_8(2A&Y2KN=bpZSsDK_7I}3yQVE} zY61H(R&CT_5Y9M8T@biI@l6~%RxRO-EOp-g%=Fy} zwG46KVx3U|j@W-~f@UDVowe%tJ(lVU;8V2`0-PG#N9b7riaeP+XVelG7H88;LZ9pNNM^sdf=`Ex6i#`+15Vnv(=xjK$pFf$A`NN^l2 zORNZ$A`+=~;|Z@=yVBGuySx>l*(uwS9C^jfkGoGfWBQxP*B;5b%5d!Z8Ej(SAWgbw z$aq#l)=o3}>&MI=5QAQy4XN0lQKeqegs# ze4|1pO8>6_6u_E4x&S{=g?#a%a53U^l_j~A2GaF0t^2GR{KWprQbC?u`5*%<|JLns zMCp_f*;V=P2z9}{4~hL}pBgcKucVQs(SOn=ilN2!tM=YmRJcRY@QP895rQMetx`-N zqC+aWyMndg1r|I-`Q;dvOVx{ir-qrutRQPW!p~ZjaZ{>;C%*bxtS~zIMU4D^-Z7S& zI>C?bp?tsr=Rb%!WYfA+z^T-pZH?Z0gXsAvQ-_`;_Y4ue4gWRP-G1xxE^XWE5l^UI zOMxQjD51zAv;*0~X%B4i+Mdo{y_+E@%dQ+IMib$J?FcfwtH0hsH?3SZz1c>szmMxs z7VZ-{R*Kqcx$}*Idy$bcN0rM_MBonHg#Ugrqab(YGMtka%-U1Qyj@2$0tGcZ&Ra`| z>bS8STvR_H5mv$WKR+Cu50HkdK|Fb1H@_~8!`n&9&gGs7)Wccmc^EIA=e4s{7U%6^ zs`I?+`-_!(Ae`MQJ=iQb6Eb(2H8z_`V90y*3sZzLnl+gQ@NS*dLbEr-ubOn?>H;(w z<*02`BD*h8{rhb&xv9833%gf2M-L+^-p>co%oiLtK@9?7Htrxo+XAk$1d27RvU|fyfF!I%fC*y-tzo51Y%Y-z)GHb~%!6x=gcFkzBsU}DSL{V& zw<~64w0as!1(n4ZD8Pp z6WONYW=x5F_ojhuUD(~bFZ+0{XsHrrUT^Z*BG4wDTUq7k0+ZBnN=CWuW@bb$f62!s z$jCR9c&4?rY6M`eqY$#RbTZA9O6$$97I|80y%w98)gV2vSzY+dK4i@ofBo`btM~yf z>Te%+K7>hAU~(g~S!(S1#{E(Nv$YJi52ZgS{jnKZgYqvwsci{zqbnrO{0jk?=fXO; z$|_JRZ)mYBx|gqfHc8%@&x_E?;eVtMn8K-FBlIj)&yczL@mW9u50wnIdIlV9jy^Df3g3zg{>M_3kY6F><6w(KI1RK9guIBxv#fQo zXspOvLgi1JlA&S!a!Z~fwHa<&#`Tmt1U4Ln;sKS*j8rbUa0t1!6WmkkBm?eOXFnPp zBE>WjK~b`GOhoB@%v<#fi+o*MpAu7)U#9lS8U*&DnvrtBVL?rWjz@*E@o zKM7#Sw6ssPs2;evmI}Z2WF92ky}=7&dr@qy<`j#&V4oB9b<*KYW(0t76{FVD>I zqp3?WVZAJir+_87!T{RB&5$>V=z@mO%S75J8>L;xw^JJ$#`{x==#TkWm}vXtL6SDf zLtQ=0vDF_<5A0x%*f%8A zE8p8Ug4|-2@6cE)t8Hfq9^m`8_}s9YAL&q$KG(zJ@iwaGr7HY`(3ZcDT%*osicu(P zFMrJZ?lm0CqCDtptVRBF$Hk)zpC?Eu*u~IwPJgnWM7`7mO$!6D$!63ya@P)t~ zmMzn8Xi|SzP|ni^>Re4F^8>D&<@Lr6CE+|)<8(~h15ZV*jv}L4bg3q8E^JvMBAR%J zP7BLxGva09UROI(vkNfq{2;39Seu1yY{(aI6*!u)YBlbW2BF-yT*Kg;ep7M2Y4o z-LYVZ1grOc)g#N zh^C`z2mjOX^C4}G^tkUZj?V+?`qb$R$x@|6tX%U{foqGZc7^nKQ6}0-G8Jqn8rSxy ztn`lp90bTCBk0QPT(4oV(yZeN#V?{GMBEn~DCIEwv50~17(ut2px_QE_ zj}km-|6eRD`gX|4TMXtKDZU?As@(|5TZL`ro8IxtX{ap9M_#zlZ0wz5#vVr}km1ZVd~H%0wHVjAI~Zk=7VQ@fJS(Es-n zNHHhHBmz}|D_VcdBlILED5T_WNA&Q2^}Z`XD!lU)BQfl0&RPAe!(yAn$cE=-0RBo^tBLlkzf^nB^X zYbSSyYk<`q5z<=TmD<|FE4*r^MPoTP>scrPu*cdet5V!jVe3v0wuRq}l#@jq2OR)w z5qCh0Bu7w3&ds_8$1Ladm!p||%#nny{8i7d2PVxkbRQsXm>7VHtJW>mi4)LO?Va^d}_ru9QA*<0FBXa8yix6}7Z%W0Enk0ET=`T*9{$p7-UHq6MyA!J&)5i{fM8v}0Cj z@5GOy5OZIk#^u1UKK$fJ-BV;#;Hwd{cGND?jciy$ym5I^9{(&Akgy4r17h^+;;9nm zv{vmhy3{@IYCHmC>}|DF+TkBao(qy3*DNMD34Bq6j(Zp+7=38{+K;PT-NB89aIWK@wF;la#wGurHsJGphEiL0e^u4e~0$Wr8QO!s8JAdS2s!SHY`&#g>xQ_~@gfw3~HT0TRdK4o&^#pm_%<%2j!{(;p|&x@9DH*U1Zp z8`+dn7f4b56+&)+i27E|_`2cpk(jxyATz>y6s|`q2Bg#V2k7=tkn41+^H;7x&C=vT zVwt%P6wFn{)oo1|bVjT?-dA^sd8wFuY~)FDAKei8w;f z2&)AsKd_FLVQ|GH12-ONI1dl9%?tz5)H-abczm`jSqK?K+wU7i1F(+7k3W_lpPbW) zx7|n4>ty81t`-*tD{jmNv$C}5^`aCB6Ud(NB$2~j5=XV!?XB8RJ7iI-8o4h`BL(48 z1<=YM+HS+@Gh{#*sTzr7*EAMQE^mKo%z+({nsvA4*^kpHua11Bbm|DdqvhJVMzWH) zWNRCY-Bf5$uFLTE^%c^8S-^R9@P;cLri=kh%SH_W9v0&KCB7GcS74A!_-7-|2=I zj=)yg0(Wr-yZuWYWxgyK^U?yLul7~M*BB+<%uK2kl_w0;>qG^QB$YfwY9B;$Ef{tKpPEJN9kuHR(gUr6*|B z8e4up*&Oyv{WFjJ_sGr={$*!m7v}E3{JPg*cl0Xz7F>V>nL4U$Rh-6n4VHJO?bkL6 z>Ib`FX9Scd5vUal}lPQUM41EvVW~VPo%8Bi%Zg(MsDenE2wr@gx}xWxwQA*U7eL1})JnE&gYz zl9BmL{hq8_eaS@{LVWm!2P-0*3)){of-0Lzjr9vTDYRJU`|H92cLz+%Z;gp4M}na( zk?I@+OhtW9ETGVRvP(#Y!kUz5aKhvfQ$v?${GICN_9sp;`3KOKzd*PldgZm}V`}Sv z9@p<%%v8hDil*8#(JSYVR+$@IOtyymR)Kty$=x_X^^r$ z31w7QR<#2D(_T-|{aCHWVKPmr>w2)y5)EX*OCHcviBX3E7)sYH7)#X>V0}E8AuT+e zGTTZcy4!Bu=T);-q!=3(%f|8_1+{I>-~~#LvnwZC$m-V*mb|bfW?2ZXpcW+?f*Dp7 zVeK+EcBO88v7+U$i#Ugxm=szy*Nu1dHNWB<6;(!a++{O9G4dH+RT{a}rvCMV3B-j7 z!1#uo-<>0sNQUeBV<)a958qOTK4Q&|P!w#)2rAFnu_ZoHpRiQ5#*0(Dn^jCEY(T=+ zX%fN%zTF)8EY_-l4~cHNzNVqU8}An3Yr1rF=4o zryp}QBsVqdpU3C}1xU!;qQDaW0MD{8TD2G>{Hn+Yrl={0+Hf*S-t{|!lPj+l-kXKPDECH#M2um2|TIDF)g{Md4kx!qF~|9M&Q%QzWI;tGAe zg4yDv$x6CT?SGklA2EQygzA@Bolx)SA0UKL|;9lE+DQ3r82${EqW5(SK zB1?)KB%HGl#E4@ zOVC7CjIMC}$OTZ0B~;)UV!XvJq>V&W7Y>clFmIJ^B3EHn^m`pN#5*tq?>?7@id%~p z``I(rWFz!3DfKwZUY6`xp*S1CU*#qWBS(7*-Rpp4h*u^@kK^$z1!kg42Ec`{Z8VoT z8w!;=y7a^$b;B>LYYNs%sYM`RJAaWk1^qW;5}c+P^`7*5WhtpvOt0~4$61)3x3(F_ z8Tv4gb&#AR;A(xmD4RvyUg3yQPefTv8*RubWi>(A>c2Hs<%Sp&ATx5&B#L6 za5(bcWn&9gM5QhmU7R&GiX67j1VVQ-&VJA8BkAUEiNC%&?%dH#SPFP$iG7c}7nni` zcmM;ZPj7`4E)ws5U_(fn&s_X7As_>A$Nm(b$7K6KlVSx-O_Jdmc=Ux4huEl#!0U^6 zrr8$pL=h&w+UyJLof(>+%N3_1K1R-+SYY5{$1%D#mr%S|MD8pD@vUUrg)8f4%>1Y` z?MARhxm7ZA_MJMQSK;PU-=9{{MmuY>_Kd9o_ZVeQ-x%hedrPe`P?NavqwR{^LD*p= z?H2~_Ulce28}fHj8W_51KkUT1j6kh)R_P$7EB29dm2*F8t)2i>s~Z1lsv7`-L7gth z4jVqLXlTvT527I1q@!Q9-~TE!YOi30uUNAp?ZEFG>MzavZN&EJE@=Cy)pRw%(epPO zC1k+#(4NzXD^owd4oFbeN#QA#tZez;yk0Q7RcQiE4>0SFh31Mq(_~`~{K=s}?w6 zB{|e9W`NAnv?&|2~Kl52j28;#xGGPkl z^$=QAeti%EKlfL3+D1T~XlVKUxSJHT%#in)EIAgh2=^1POxKAKDvZ>hH1cnpTdaR& zK1+f|8N3hA<_u=AJp6uJv}1o&?(QK-J@S?*ZxwAG1iR~e}1=k;Z=o`AYsTmx!1dCDj*Pou)0 zs=8r#A`F5zk=m9*pt`B>@Bgdu{rp~Z^x_x-++G8Hcl+T#bW{Sf;Ydj*(Z!t`= zWI*5u`M8%|-;Q{(p$9jGM0}BSCfm8O#=QsZKrO9V`Su?a0~Yo#x z*JJBO!NmOBI4?3=<~^FJ;P3Kg60w*%0QTVCjLIY62@^zT|KbS^ZlI8r!|EpAe1E1x zB)#nPv}W>c5+k^sJlGqKkW>1CUo~#EQRvmhQJc|8<;=WfAb2;sgtl_rEr`%sjstNa zps4|3C~&K>D(Kdny1K-TI2?2)PBK0%p(L-BX>rGaf&T1x=dAls&3^{+g_pI#mzL^r zxS0MhBlnluxoP|t&V#z@Ed~K;@d>62pG9bwc z+JMkln8vh}B!gVHglDTKw43SykAaRuZ}&05jNKHA?Z!su=FeT778qRz*O{fK49B|f zu2~G23mcXG=;C0i{toM};`K{Qoh9QU=P&a4i;O$4c-T=Ho|9eBG#!#;G=mbT;^7cj zvxam?Y3!1mY72ak=N(d1N@RvC9W=m^;5%YNyp39#gEfYOjxjYG&H}x=9avh&D;d zAPckw5|=zvgTKWF_1UAA(XA;MB%y)ZrBn(6RfwCM`2cyp_kuqLNahXK=n z`(ZF9{{j@J_1v11$54c@ms}1fF|ovtQLH%O+HGZ-6|&KR-$U{>5@lq57i!mIm~bDe zBv8@)q2>iUpDKl%2Yz&;AsaL>jFOyf&Q>Q?K?BI+a@sMJx(OJw;93PVZI7++qIh0^Cnk#sZFFEI|kRMo#&_}J#bm`Z`d{m zwDy6~;6<=womX`b{fc6x-$Aw658El|H7=RS(AhR_+bn2o&;+T_iG-?M;6<*_H-aYf?rOz`+44{(0) z$|Ct`t$O6_p-`wMHG>z~cD}gg+o|pR2}epu!Dc>8bAg-ch(OQAkH4j*v%8C&DGmDdE1<9q&W8H( zVopxgVq_Wegl$Xtt9Nlh)JJrV)|+iQkc2IfcGe-;h^;_QwmX!glM*AxB7x!)Sj5(P zFq%E~U*F>l;Fo6TGPkKm-l>ldBfYj*b?Ss3j82zkI(x*6t4l?0cwHpHE7)#g6Z$S;)t^|L(VhW!AVduXyH;OQ@n50?x{{$Q6iN?60eIV518zY&&XX+sLt}Y;?b<+67B?Q z21M?RyXb3;cBS?Gp7wVPbf~8&&2d|&c&~#kj-zMba$@TED;>|N47U!mpfYUw0l4HU z5&yRKPmo9|VGyKn$m2&!0pl>UjWq(PiPYB(p2#R*M0WH~{&^u9K|-@RV+nCY!dz;z zVtF57Whni%?U$t=%pf%p(hW&7ak^Dvr6V5w*dJ&_rG%X?cPtzS3M~~3WC-Iy z$kbm70_5lRQB_por>ktaD4x9jwoO^IA}b3g5Lt46JzQ2-xPLxo_<-H73WHF|7N~fG zWbQjzUU%ryzN;c4aISGG_S_dj>bHU-1R1S$RDN+d&}@m267VBaGyxGPRZZQu;uzNQ z;B92AzV`$dQ2)eomTDN^$zAgbz?}iq6=3P;k*6KEWC_^7gnZn^X>wvodf#k64W4H{CAM__)MB6aNdOp1kcNmto}^`;)tl~6mH6cRd^0x0vH@mE?5`|R zd1$sN3qC5Zf0kCKfSL1_dz)O3Mz$4!UfQy)8WW~oJsP57P01= za^hY%q9c#}<=`Eg71-@)7S``e#;nvGbB{WM0_3&e@-V;CrLK%b_IE==|oCgaiVsS0Z z`e?3%+Dw7ds7Rm4TGHYXR>5U45ylrR^&_`jluLF5O>VY_>h$GBuTAQ$PWurRh`M2b zd#ZPJkL0PD+NF4US&@>%mK&JX9-Xd*SG0yGVzyC_Cavh+27)zVSMupg1M@IzKHtLB z%XJadQ3uj4B459aU0)8c7n1ETD#u1`GN%P(K`(_S#mgn--OwFzrbsbSCv0xjyh|YE z%221WG4*x~IhpTu*n%+lpv;Z#hRkb@>7R=l{GlOLLG$D<33}TkP0*TgM)rl9^^}e( z1~mesh38PVuHt|X#n6q(Wam-{41?tFfQeMev|&ms!|ziLCwel*R@L|XUrbxavgPDp zjxXmVF2!^r^F_?#py*fw&fJmY;!TEBZ++ww{&csPIpxY*)QyJ&h74+{D}rtHmEiLb z<_nO=EJc<8L2qw24mZUJx^Lwn+CH`X)NQno%8k=L$pqY>lGWlD!j0o+ytz6sBG!yb z)%G|Gc7Xlf|9!x6mpz?prQum;X2Qi5UjGRIRxA1aC8zRs&gsRev|FmTCKa2G&bMdd zfXE0ih?L;>7^vD?3v)#$_TAwPmk6v0n!L9pA2n;WUjS^RfZmxqV|n%L>?d`(k2MFH z82zC_F#GvfXJ1|kp5haH9^`W{;T3M%?gnxvLz_x)p5}e$XKvcph#rt|yoND!b1{H* z#9Zu?|NZ(M>jyrc*cW1*wf|MQvdC?G;FTn7{|#9YA80s1TRp^TYT~2W&0C5X-$qr`)(poZ|4GM89j?nYgrIAO^5Q5uF&%SEVoJq>p2e)&!Mr}Pm7?pMjc`R}m3)qSPk-r$+htjcd9Fa8?5kr#>Z z&60PLPlphtbYNm6%y~H2&R4eb8-ys^Y;w!LTyF+XvR>V7D?uE0|I;uhb##Tcm`Jj^ zOp+s2>zBjJ5dX zj<+t}%tn_wp}ASo7XpE*4h!S=i1~|5qeAp3+2FH~2nXhaV8LOLYdYw~+F8=j4I?9rrz>*% ztpJ;gS%YWCd=l^Soh>tFwQz|%*NFNg5(p7gDuBndG1_iVcvmwu(vhc700c)`6CFNE z+@-RW)rk%Pghg*)UDb2&PE=A?L z6YFFhfCP@_0ICu_-2g{v^rX@m-$Fy6^}23=2UQ$PsRPiWeM7J#>$eS=MbLtz4tlwX z3Bt$<8^73>S)PsI&mP_ouS+ec%~+l_U=RnnwbH~Xx6a$*Baa;$XEp9nQ7WRs+9oRkGXS0{ z+VmaTz}t|sh+tH9z&oy5NLa}pmcwk_5RA${TcAYYj+lGfA2XlNpbL_TkshhG8=qKn z)^8#S{|lj-rH0z$e5$OKOb=&Kqt1kGQoz1s(!J2JLN=mBzVW9{Kf+yfOt<>fh3rm9 z))AGIp zsxUVD#K=4&NN+S>CaW)@N?k18p5HUQ#2irA78%@qe;-t(HPljWeaCRRvpgwN1&KMk zQpl+`Sw#HPbd3?;1s{j5sbApl3yt}O&zG#t%O#Nafi7VqyWcuTl#uOmGUq4p0aZ36 zWk0xzo76fLSDotmVRF>wj$UX6fj&*%O!X8|epTKY7|>*QsOL2^)*%w9*m&%G=3sge zYEHO|&kqWPUcc~xKzqto)I|IgogKhu)Lsiz7{9WkEEg63EWt(~naDaIe0n$L*<1Ob zz;(-LF2ZgRB~Ki{;R8SR5obD8$Oz@TTr$LWcBpdGME{XKNN8{Iu9$Z7k$@-@N8~V! zeEkNnzJswWxCw@{S8qxaCrf@*eB6PN_i;Ia>*xoY%1_|2aC4G04I+0i6I{dIP&;0B?^A9w&1urwOTwStjp_I-Cdl!@2{ z2h7mH?4t2#w2Om6u)Rz%7s*uPO4{TFU12F6Y5=W2Mt2IFXkWF>u)Wa~!Yvh3P35~^ za`{sN#C0Poan2y$x`>r;%+Vn?NU1EMyL_tbT%H>+99hSi5Xpi|cu()6(Qx3#MvY%e zOg;&K5R}qjw(}Un(f3BQ(YnBQ)fiYYV`8f3%~G`I2p2Ec42L|Glfx$Z*T~9h?w>AE zEI$D5&qI9m(GK(4M9C;@YI9XPsi;{nr6^c+0&q%EIWW0_&#H zi6hi?_pouYNNSf!kJ`ajKHd~)PjX9ka)Z)t>Sr(5VLSL1P5xR!xY=jfEe24GB#%{2 z_D0U0zY`OML|&XOUfZndO$W!tTNykP^0&zIwa@*BzE3V4h!JA+Ul$O?Z=U5xobGR}j(HG1z_GL+MaqvJeM$LnF-?&Voq2PgjOnu-bHeytNK;0}U7M~$!h zLssA(Qc1T0hsp`*L2O7ZVcnDWKuFS`_e*&{GWo4QafIakf5&WNVd=RA^_G?$MXH~i z8x`&s$uy*)+4mu=az~VJ_?DF_B}^_feNk<3ST=KQ z_um1-%ZNu%+*OudHxSMmtJwWU5drJxHmbAx?N)1A@9ZpF=i%Bo&mTmSPCguuS`337 zyRKD6l%6UE@vyYfz}XdnP`{Deg7gHsB`O2OY2dJN!P|w6;wL&zCH>D}wvcP2Z$)}C z+bwoD&AoW%Io^nMhJrtx;j{fsuN4t@%DA}`i}tA-Ncd0kp!9wpof_Ae6fd6>|A`O4 zsi5izDQRWZME`Vxb8WLTcXdyd^Pg>LVJtxtHjXrJq`?2hv`%f<@7=*gvt?MgyINSi zhwnhz4kEmzBmH;onr_^MVH?59Qo*f|2mU`be9*O2k^yFFk z^fgMoB9asb?N2%=KFU>mZTt>0HQ@W3vm$qCJkGob#*UalEdSBItO!ur@$lMW1>*<9 z3TG;DO^&d^DK`mv&%ep$K@z6qYfmxJeDz@Ecc<4nJr#O6VJ>mpYWuzydj-P0rc$6O zgL63UH;vY0MM7%$lk>m~Mtbp^lA}UX1{q#5!N{MV$az2$uWXKobp*R!>>MD%F{yFG zQ50ACETN9$L5|jSIDE5jKR9zN;1jgE9E~YKg$k_MdAXrqV!OM zy{D1~CLY=Pg=b6|oS%hQ3s}G96|-zxcP16i&83|eB*n%|9#{ukGwo*dSsL#zZt*e< zynGC%bZ+%y^xQvgIDW-06C8hJBNOTmdVzWQ@zGMkdPEfEAz&-lOT_mQm3${`j!K%- zEV+?9C->&&I*3THJx(Bj#~p)WnIVGhh@2(ImeuO3+E9jb!zgq?uxP5QH?|eEo-1;cw%(#fw@=38nztB-ITWgT0St=kGex zH~xfkR%O!ff$00tR4qtd*5pdp!#&eysU@oQNbd2yz0kZ2NjpAs4<_rFOP~fZ18&Cg zQrFj??H@?KmTp|-2JNSvGe2y&?NHpN`=``v-7A-rv9WZ3HvxGm@tgEsaf&9c0BZ)N zf^zV^=h}G5Xhu1t81P>!Y1WGByF47LFZAyAJY0IdfQgG*W5@!muQTFs^@IcwCX~=K zY1RgRmBSp~#AE`>poQYpn~KV*MLxK3Pg{Y$uUnx}4{_kFM&|Z;Zs1;HRa?(?v9swq z%%~J@PGnO12hXJqEef|v>%d8dqTb%GG0mtdq1VrNCPyHy2!ef%NQr9PAMB=Dy!up3 zuBx$BDyfe>du*=!loahQAkSgGu9iOnIQtSNYR`42@P1*l7!J4t0R#Eom2exMby#? zAb=n+A+{W?FLCjXy&8!R>R`)9rf3$`p|zOUq|KvxN9i=DSxTi#E3@xWC$6w1JD~(F z`7IIb*@FKftJwIoX+FAJZ*tKQSu`!cH=f z3Phq+*UU7RRNQ_UUmP}sottB6ZmvT;n@g0509g)BHTil+lacKQRZSE1RgqV0ev9Gs zKOTV^h(g1czdLrAem|L(7~8c1CJQ+kz0{BAD41ESl~q~6XXXE8BENX11YO4+)0{rq zP3MHH)P0JGmcvLOj78$KSQsMVXW)--g){EH)K%V>i0K~>ajAF~%#P!c)y+ph#%2%w z>>oKYiCY4Jq5ukszKYhlFBl`#$@W#EXEqYx6$*`bBTw`sQf=xG!gR7>0FODDP0!FW zJ38AvD01SVglFnN5330o;w-zAFTADn!RE4gR@*i6>J@W&arj`uaI|>#LNf1$KzVRn zqKW?BI$Tn8;4PCF*DW7`pel(f@_z)5lRIqOG^DWdh3ElWJ$&*oU|eIUee+V?Qleq( z|8{0#R9ut)4c_%EEs5Flk2ygSYl$(&nCKm)@tw6dXE(hv*@gXAV4LzCF~1*lC>_Zq z{fqE0pv3xVUr*=Zsd}VT*+yq^h_3-fSU%h}!S;2@Fxq*0!+A&X(d+#+tFw(r(JlP0 z0elw6v;~U()*0Z3C@_6r3};>G+T!n`D&*MBoB^+m+$I0|WM;m-CnS#=^TjHo(fx$6 zmx85wPP0dXab&a&ZE3q&P;(cowPT*-%bA%V7!ov9Zw3uC@UwD|v5R}j$ZH~XPl+Aj zKggq=?joo^;d229X7 z+4&wK?1UCkDuQjia#fFn60$G;R)~5=%NI43r_j6CkWb2_kp{w=_RpM z2hp#kPSd|#D32iklc!J_Tl6MqE7pF6R_FGGzoAWxWgA?1#>RC$%iyvf0F)v$eX)K0 z1Gts4?vF?XKU}3>tB%7ZO}B()lx%?7gEg*|qaii-4$y#g)Xr@NTKN!o!A56SmPy$^ zfb>U!3>5pv!h6~md$VUku-O(Fk}dZ6h!6=`XWQ`1kVFwvTKe1B6(z=c=F!vTUhG{5 z65$nysA+;dd%mp31*&*)uX6oqk9hxq30_p&uoy<1bcAw)J4J}lKz)(5 z9i)n8mAQt@_ya&XP7d1rNJ~-6vxOzO5mY4lHo{~|sYPlSQ`}FA3x9jYLx&-cw9XDEmr!F`0+kqi9iR=U^{!x3JZF6dv zx&kqk=jl4;?_+`PX}(J;3a4@2SL38;mO}iY`fSHqmLMo z%KG?#2MSZVkAYH}F(;uKl*!GM^K3*qS;u-kf{#bB#>nFc=6MOYtjpnaM4|z2&s-qM z2NzMhw9+0+K5ddeWFZD7}soZp2>s~Y&-(=a?+H$!s6+Q+ZQKFx}~qp(7+X5E6U zOd80ui6AB-;Rt13S_iesbEfg@alCU#Y`wnD7vf{Z*n0M73Kg4$Uhd4)V44i3|FLhI zKuvCTBLqG)6;&tVbtTp{lRgq8Mt^)sJ$|#~dU?WLO0)#nu@(E6Qfq2^tt$*&G7Z_8 zs)XZx5trMDW7DZ~WVYQthP@iuKhxuinXK63hn+*pUH8IjNa;^VCI~UTHHf->b{s_r z7dA-|OEQ;tb|I+0*4Es3gsA^pQ8yu7U%}bHksa{ zxm7oj3~}g-n3DaCWanBx2C`5x@p~@9d*wjAe|Rg|WBYd`gx%}M=UV6q?f1Fb`ABxw z=d#a4v?xl-S>C-0wi(R1WyH6*<+eY6_U$7&{V3<*^`nPCl5CD6vpiqvK`+G?F<<|3 ziBR{&Z4F*t46TYX)C_)>d+6viW)YYBuZlH!)1Gh;A`Z0S!Uj!U)lh?Fvk6kQ`x+wy za8jC-j@~F^tK46*IHecF^929lBWLLcIAB_X~j}VETP9isy;%MTPMUSqwDSQ7T1uLqUHl z*o_=j7f%9pgMHyU#E0lI*_9trWqpS^A+-tbJB+U znwl`bc`K`0ot6g=YP%53qs8;y2C)S&L2=ed-679IdlldLB8cGX8IDPFM&~0FuMj%G z2iLWkyMD@x=aflf_Va8RA~Ag#=G@=qftUI&#j{*{2>X7W#=D^Ba%#$C8!<>Dv&HJh z$&CcPm?H(yU-epNmnDb2MRm%W?L+U5Ya_S5XBu>nuaU4OlJG z`lj5FPN|7g_TA}R=g+h4Zx+_d1RF@$D_7+%mFl`!;)i(DZWla1*Jw5M7B&PDmw(WX zf2&h7LRxgm`Uj9!+7_$&GW6O%(NJhGm(g8>3KZ&AE*48veUy?0c7T9|mTbw#VDrWx{`~r~T_FiG}rb@RMwyQpM_bP!+22q`M`s%ajAkb+Zz9DG|EttSM_8M1>;j_+88SoU;O43tjI-66@X}VPZ~6aMz*2 zyb;Fc{3v;&8VbgDBBmK5N@%l~>QF#+61TQTH=b|{21jMJE{M*4!0j7`y(j%W%03>y z!yY`-ihCG!0UwB9!)~LB z2LyMtN>UjXU7d{}KkgBPlu<few+zxrm0E(P|>L`g{7+1t)5A`3}c{D%pNP8&P*70CGXh;_qlD1B0 zNDU}`gqlSO$E%*jSaca`6pIzUsPiVzHS7(EbJ~5sFW|x*=kpP6mXU<_PLSgB5wOfH z$H@@M2*0jl`1^Z?ZNI^0En}|PA#gGqk4`+~O;gJ-j>6~JX2Zs*BV2^U~u1`*3AetH;q}6a}?rID`Nx+v!0n5*)xJ~bk3av-H3kO^L$t! zMxLfCtb&vOS8Xb;t}|Y{f~QIowe^~`j@hDB`39{p&$#aTD^h$8DTUVGO7}dWUNSuu z6jTI6TKH#chdCM&2FEloJbv)7gz4h%J(}t)rMR?uHWp!PeVcOS+4&XEIH0fUu77^b7iVlb$4NqOy7M%jEJ@dI{#uW($Z6e-$Wi=Z z4>USCNAql}${CDC9%IZI&NHllj@R$+K`5?g&r`Qp&Nk;<7v?jV8|ZiZ@dxT?pBj}S zuq9YNzsluNU>e}FGW+2hpup@cY}x2+TP8JsgOk82Ac!evx04YbrG8jD(Ii@JPwF z=U~C{f-N7FVp0XLQ#DR$5X}5X`}-OLLaUF|2mP@S-1?y=uRy@Qv0<&dwTL5z;+z7( zFvDM1H?Zk>gyN0+v>D9aCoW`gaZ>dlJB5+8gOvqm9}Io6(puI$t{iH;-7bN*FMk=T z!mCaPo^V(76}RhDdGQffa8sX%M~#+@#w(=Nlcs_O>c_(^nRsx8P!dv zploF#kIkZ5L`OIA5Cc(rlDQuNw`KfRUW3g?kF*`7Ku&}yL zpB%45E1B(wouOezWCl3~0)88K6c#+!dRqXfyPT9a_3JX0Zuj+dFIJ3tE0gYi6VBsQ z5=D9#ro1T1p7slFq(0g-b~TYUo$H@LcpEhhwu1IDmhC8iRI%3QI95zb}~w^*^|MLt|5dZ@U71*5cd*kUsp}_K`9$5b!P(@-eln4`f5F z!}O||bskg^-~w~wz>e=!FKo*Hjl0 z?F02rf)?9v%u~W<6#g#=R`N5z^UmvCYgNVRpQ}smnHN_R8CE>Y7son+I&>?I(}Y>U z#C*D!q3U_1Wk$R5{rSZ66v45Gn^2yxij=* z;GpAb)45)?_4%TPCv?+NsV0!LPOZC7gclT&4IsQ2CYS3{!w4?fLMib9yro&h-B}Sk zxNx*42=rn+&IE`0bzsln(tes2I1IDEG1YRW7!RWzHL+T@4lqwr7p?M*q!5%zaI8(M z#vnx;-9bKv)t02fyayBzQkTc4GlVjGtp9$86H}oi-jN?#-*4`~BDo0ZiS(FM;d47t zi$}^v+^lPXA!D9M(^&I_w+-y_RUz0?6dvV{(r}Ej=7#n&a6h+;lZ=e@FlrggFn4^h z1**~MAgs$z{<4l)@r_1Bh8P-mEOd5VPgUHO44(~l2uc>V6+jsctK8AdUFuGC!#1rK zq}2UrhR4_g>XA{f2wC9s?LP8UUB5KgZ7=?VCm#Aa+TX!W9#Tq+>P&SNOAl=7C|wU8 z(1WH?ORx1b5Ec$vBgz+-NtI%1?zs_+ePF!D%~&*J7MC%04O+Kgq&Y>Fh=d1V$siY4 z88wzo@>N%VxkT#2Kwk6rwi_?jmz#Ky0KOMH|Kr#!r-#A-eHaZxd5xddVjlMNp@5{i{!RzjudT%XfV!CNlC|X zkqBm8WjHXmY_t%_lg$=;Os;s>fn9PU1(?9Y#o|D+!#opIcim0uaVV}{Uwf1B_9Z@{ z%6guHjjWllTb;ZLifQNE%nszHFz3R$o;CFmV9zu#l^9>m&6`2COv_(MO6nHRw+0a* z-OtB>)&6J1k=wbh5hTG5wBtA5Wg|3DZ8(P9UvH^$J~Iu{84p-=U}}gP;Kiq;>581V zzt{+8z9(?nnx8c{c0f1of^hD5C&h%#tgjci@EaXzC#TQk&(=2B#| zXX}OQ-BFvYsj-G^1AGWu0S{hT zt@>CT#qf_R4?SsUG`MiwKJ1ufXA{spG$7l=wKi4trr%u^qI|!FO?&>~lfU}GJe>Xv zz&-%rI|}Cm2%*~>T^$^GA^vT%Up>%SY|)a+Uw6Hi`Mb#>8OU`mkdWjX-oH>wpXMq> zc%2<+(RD7Ao4Qgmol=@Tqv)n6Jc^AioEWIxF_=oiSN`@=!qmSi9I-7l`E``h{}Kn< z&X zeeuDVlDS}$9vwr?`-o%NaB$Sr>o)zpr8MSX(BL+ef85FT-l=U;5*EeCleA6>GW_&C zSBjwcy}mEHjcr2|5hPD1qd&}_>c$OMh#MN7O>o{i^HwQ_0Y+}Bd)_~4ftOQODdXx? z9`gVj33ZTxdkQ6|Aj$DA2Jj5874g{{tZecWhggbQn>*ZAODf z{5)f@4qy5fj244KH$IW7*>)k_X{%}q>@SO{KG1*>4NRiS0st_RQ<{p-C>EY~p zy>zp^e(LI(Np_tV!?x!3IXSZ3c+Z!Ot@zSZO$mUsWI+{r(O`tZjnK+v_MFe$L{%_P zdMjWt)V7ZnD33;E5N z#|*=CrToV;`Tj+BoV6Im%`gc5_AvpZ-xItn#@5i~W?-bZBsPuj1wvYgL11wadsug^ zHUr%}i{TkWo!%4*PFycUIz@Tla0wB{0ZWth1H^EcQM`->LVkR@iW{ENC-t+spo{GXHOP9V&PnsqSgQb5Y zqihlJh<15kYA{6&tq2gHv=HjzG<1e2qrh- zv;#GFt*d4^NeneG!tT#8`nY~D4x}iwH1K*LEv_P8P{`Y8nL3s*=!&M9JEze&?;P+f z-%kc?*yabjQ1iwljX0r0KW0ooW8+o?`E{9v5!}X1bR({jIoDoL0W?RW_64b8vK4Q{ z)!_?ixhl=(sc;eM_otdC#JVZ^(rS4hn?K5>D7w;oXzon`cjr4rlWYSxDdDre3Wy!4 z_@_|gsDtaUbo)1KKtBD#iJ;D-dq9xZ$iR|H$_=5~`S1GEHN3(b-PKZdT%Mg^710|` z4`AUwzA_T6k4un>Sc{h?Kr9n^4Y6cs9s7;}PkRjTS;#tha2vbo?C0sFsW6nz%7^;?I`2E}CJ_1;=oU9pyAWwQ zz|dC+(7|msvYFe2^^%&teiQFl9b&$1kZ?n5s8!Tii%wu%aXci^h>E#r(BxCS7-8d?+_kS$fpf?bDkU_o&8*BAh^ zHDca4_3@lOW{98bEwnbh8hgTwvZ6@(pb2)?Ne};#P3N3u9+lP|pASo2eWLu`%UX)@ z9vBng$pAM%$iIoqS~i+10UGI`C0gipguMhQ6eRcIF`{| zl$_-tKo6Q}Rc!TrKim@X{HJWh$ZP&$K?;97>{CD21jTl*`yd6EVkpNxvn7s{T&gEf z^&66~qRQ`H-;!5Ew(EFiY(2Ql0i!@8bj=FA7hrdZhL%W7V6L=RuZi0~s_FTvz0cs! z;fV~tOJ=^UaG3|0KS|LsrIKr_(k2ts6Qz)Ha<1EhnGCV~Ta=2;tE0R=Y@U=h#6^$H zR(7&??n}Yz`vSa%K6`+9b%Pcf^366P86z-e<(FdvU=*!GiGJgW*$x30m^xA0rNUdz8f zZF_?dCnaamP_k1nW2E1`wi$0^ue&Hnb-jyhAc3t0$!Y#))oUQ=F748SoW}tNSU)Ds zVYEaZ3+32r2M{5zIlRMw;Mkq9KYGGp)Y@0yKhc%+9nFZX#b+v80Ose(XY|hh;|*_q*w?y6_4Mo#JxmX8N;zy zL^fx52dn9>U346TEgM_LQm7H)4;v8`0&y zYC`XmFwBMmfLo%tny?C+&M1YA%O)RFsgys1j4Y3YQykiYgPuJqEnP|FsIfmR=tCb& zVxRzoJDcf(TGgmp9$ysx@IvrrzAf{qk>pRZt-R(NvjDxTHiGd|pBNthn_g%r<4(;R{=k9q1`X7xwOA%`6V?Kry%#UTASQ%Ci>P-P!KN91MR^4`?c{9LP zMf5VpWWrD#Zscs{j64htjc7xHgzCCzlsg&R>kp7BF!3R#Z?swMV}cB5WoS}qkjT~% zoi#*Zq1(QbwbdnijRx+86ZqZaJa>dw_026YJ6X4|D!(rt4gUgysJuMej-0}iWuMl> zUrgmp(p)@|&`rU%L)LP#_vJ%b$v$~z9iw@5Yh6-RY{GN$-#tFy?l32w}Uyr#40Gggi*py+2L!Ksn?Nh!lmZZx(&Ki zIcvHi_VpVJ_g>0v`w1^2Y(!x-#JS#y$Br5!Pu8XoZ~860TdP|*;dW<=Lp;N--K%Cz z3ldc=(Tu)Jm_M2jlWp8ZuDMz@2@N?EfWW?@;B6P!lY(rR2?!7=FjNZaZ5`nwzEw>DW1xT zH57Kk0pmDq8iyYRG#X7WzDuMvZ6)?rkjspfzaqD%22B9~&;g;Z9-OaVuo9jZ3-s5qCG4Cm(v?AR!A-6Jw*w*qsP`5{B8Tgcg_#t} zM%Q$3&0L*bMAc+D?1AirUB{Yz4!-;zr7t-$8Fq$RCT5_Mny8p7dbhng@I?Cd&@j&w zPDd_jkz;3n1ESGWkdY$u6(%TkXGcrnrWl@+!CtwVS8G!}nU5K((Kun8 z3U3OMUc4C?P`<}+l05P$5qY0qrlftXA(t7h@v_J6yj*NIU@{1+gFXX=0F`eD2VPm? z37h}uedEsA+7Kxt^GCwkOg2;;;Y2Q@iwj=ggmix?OEycHn#Vy`-7z&+w$8jXC%5 z?(}F1V#d_Ono}svK7);IrPn~eBR%DwhRB!gSlYW@M)-nFuqVC~8OfI#^G^6kGJNwH z9}0A)Yv;}zYu^q2>#ROYbZjAxhp@rgAGr@RuxhEP>Etm!%~=lYvLx%=Z0x4UKXOer zgF-+yqfE1bsmozND!zyMhFytpB=R_4?CSU`M>%^A+cojFE4Bukxn|;horn%weKw;9 zLZn{9VjO@$Zt$!t(JodPtxG1EKN{`*`*rt{JR@fGP|xNZK%Ci-1BhX_PnAC#2_@{`p#zRL^ILH-Q5 zu*Z$W5i$L&qCiXnj7E2E?urCC=F(yx)m)(cAgVGdgCa;p(KSg`b{^|{kw}u*_ANrrZOBltM#oL5>eIKbX-_(U(TCn4deMvkYh z4QNSqw%rS-mxhqp4zJxb5OjZ*Puy2LXRcd*-$bk;@bIf*n`={37#x^Xxv_o3O%lGv zbc}suuDjtY&L;s@*=VJA))@gp`eL}dShvD12_%X>$j~{${RZhF)K0iV@t2I5e+HAf zpJ|B&*WIuyCM;waf3W8x$YTMO-%BB04Rs!{&t9R`r zpBId0Y;quvZ%(wP20H=k74X1Tfky-C8Q_C9RjU>otU1!#G>4i@b=}G;CN3&k1euHKCkQ7rKUOy;NEncv;bSJxbS|pe<3)F>J9_s&QT#q3$-6zwa^Bw^hy# zEAc!t{werPt7NK0S_ewg5RPTPwr77v2b-7-&Pr~Q2}_REw+XY%36x;v z?0NfscCSQr$uu0m{Du_6XXE#r-yi{+Ml-TC2{RyZ3TL%wY(Vd^Fly!1f&-Zm3usT{ zNaFUm0ect}@UsMUku>+Wj*-OZbQW7Qf?B}hV%|hJk=)94JCz(8rQuT9NAZG~g7Ghl zyGgd^xFQ8-)!_%7Zz@EsDJ;pDtTlNIW>>M$B9G^QPUKN&y5E5jPI;B(?vWcHa!%CT z2lc7T(KkxDe8pcv{Ph*+l0EJ(se)riGQXBEDqSl(To|@JF;kqCr4^}_q zV~V}XJv=8v%zK>Eh1W8xUMNAb zt}c)Fi0f&A?F~8UEd5B=F@Qok3mBfCg_?Vq=E|^gGxk-G(HpqX9SKqJ7^@YW+ zrB#k~Uq&p_UN3f>X`WVMilF#@2vqvUC4#T!7Rxq^L=EJ=*Z_nEz`&5mJFba>DW0Nu zbWPKPuOHi*qanCw1X;MWd6`I%%cB2Y*Z^Z5x6I zVHXqwih1vwzONFa<}%4EoCc{SZ-)T!DQ8RAlLa0u3}QEY6xl8~43W~KRn#4X;s`&em6Yn-re4M#ta{%x@X6KgqwZ*IPc_7J>0;*o&<1A z8lEP6+K~xHgCfhouS!Sz(g15w*6~*@CBoH=?GjSx3>{#ZCUMwR(?|124qI38qNM`_ z|8wFVJVXlB%G8VZV;li$2KD)g3wHw}+zNh~GRF!0CDJ^9_(1qIl1cs$9nU0QK3Q6j z#-lqSx-|D~-7v^N=0b53zaT$mpbV5P+uvfMB>~MS+2#&m+qVA`5R^&WOw2skLN-&9 z*iFSg^lvuqyVgRchnIQM(!la-MK$Y~R2H%w}Wr8v` z&G+=dv;(0BdMkcrYJ+fRiZ^?k@(Y*kLHxegw8xHWpprmDYv6ozK(W_c=A*Z&=fUKn zh5{pNP?^E`F_l92`(tf-FWfI&uz+4(TtN>-IL3994id=^Qn{j{4-oDul`UtN2puk6sU+tl2jh+BlD*>rAUyQUYk)X;&#zmS{wS(ZrP~ly#UJ(6&;8iA2%k zwUEdT5lLlHXPL+;Z*HOAxO{)30KP&cOrg7*{+t3s_ziTDj3v5WR)t3LmHQzO&=nc+ zUfv#V4Ad6MzKYZyI15OZ>I?xAC#Tw;>>bbrI@OAe?FSc)8`}i7va&ISpc0HAZ+Q>g z_DpA1@N|Q=esTJHM4NO@gZ@_$gEbq_BeDdnFB;$g)8=yr>Bo}#@J%-z6J%a_Fkxkp|$xh+`j%-!aIE8vV+@8rD-qfneS*fIaa$=elwYxTZ)lvP6 zyzGj8Y`LH$o@iwvl`QK624;=x9GQR@@|ZX0t}TyU4GRg68*` zWlzqE8!9|?Ppv~n^_soxQ5C>gq~z9v!k${5FUzxD+&jWuW;HC&NJ8M2QXSfiHQ=1|I!2O)8XrcA8ueaT4Gcc*{n0F6 zCTe{K17)vhiKhI!JSb^?k!IX*w$evl(vWC|x*?o8vQ=O!#-bYo*%lFk$`24_FNm87{OX9fIfplYA*Dz37{Lq^S{0UmjsMhH1d?p#gN*qg>2ApRf@y?AbuG%3n5szsnCE5 zBy(qMN?f%q*`3}YIKf%0`vY(-x32w1%+bh?rJAGVK<&`l6``ARQ?e?`dU*;A+;dOG zRx{jXYSw`4@7j=veg34W!9$~{1CV)fx45cnb*p<=0pUWDxm@A)c_|jH4ZkMr!rBO& zvC5{GW}AZxZ+E;AXHCmef^lZCS!?GGNb*iF{pITK#cXACnFq(%zta*5IUF^6&uz2$ z{)bE$tLoM0yorlitHN>-VED4-Y{a?LrK5&>_^EzgQxQCSr}7iY(d&I5RQZ>j96vj; z%J&Q*lRY;$_nxj5#Xs{R{sNz+aJe9#XI;-)FNr24DcQ?=+w-Os$GZ_uX6c2Cl-3Jg zavc|VzWzEVA+ow0y-!9(>VF9mNp3wAXSohxxOu&%Tgx`liGB2SilRfpC(snqkUF(b z#q({HGNOyXQXP(I;0s5CPwxo~oHs&&Oj8q?oG*w4g%lqN;KH_U^i{cQQbjUy+wBHF zY&VEo8Jxm$hzCeDxaN!GmQQwILU~Wus9&+bCDx-)za2jol0c*UbspYL-Om(%=fWgdP% z-HJz5&2HkS^tzmOcL!kH3aYBJqQ8 zx09%nIDg8Y4Gqc3(l9v|eHPEdKtng+u8b}cOhQ%gRv8s4^RZeNf~Vq)#iW%o)9!I0iPy%Q_V^ror_^tTnV_0K=9Eb1vh_8ZW}#1uXyG*_8gk) z&fAW=gOuP$JfGP!$2dsHk{f`Kl1SO!vbVy03#}D-{0F)e#!Aa4~k7Xh8ImXD&JpfVHcd z!Gq2oIBYZyE-KEGgeqTT+W9Ne)AbI`Pva`~LydVdhrL;s#7Hte&O|OkBHSL)6nl z7PB97W@8{}8u+W|O-Z3o8883$VKav*FAyARoQ#q!H>D~TA_V3<35$%}>Namy?)JYU zVARYn0r51jo9`5XK@1dCVhkC4f`-lXTiC&GoC(ua7vfXkcK6fBZZGF)d1QS*a0`w_ z6AqIs(TcL}-)d4eCdrtTB|tAUqinjQ``F{Xd1zk+!N`Kjip4&amT01*bV#Nl3lB#`8^^kxZgX=jg1mr9z=~u z|5B^OO1bgA;OFxh4g8p*V6QtlbNNT|STQL_!eW8aJ#;1k{+!@gLX6~HOOQat29U{> z+79i*)g77XCet2}&LGdjYJS21|DT7CHd2o~#6rji`!_;me-M=khT|*jsR!wzX z>d2Q#dmP#x?w~8szQTB z8StZA4WeP-nQ7EXB7&SMy50$tb4bJC;2%=rrq%Ru)ZZR&>#bTO5&ChQ1)}7uE;UkI zvW|@KSYX$O8eO!^(FfutiN|zVt%}aTb9u6}O}u&~we-=DsJ359>x(I=yJq6K?A@aU z9ikr_O9)sf1DTgnAa~LA*xy#xJ}jl-ynPrN<38}#b8GPAf+hQrhheclPW2Ljgi*oG z%Reb({O;*gZ!2XrQfuJvKe!XgS58dZ2%xU?7@RRFjnCwre2MhGAP6qZn5LWe1gff?iU@foA9bFG50neG8 zUaSc35Lz?24wGLIto#?oE4l!oK~H@IGnlhUhsci{$?H?JO3Qr{FaPKei}27nlwUa= z&z3$Gs)32ZzjYzd`qSj|Iuw72^M&#ne?xfQg!f*K%a%kitr^0Nm?V8r;@q+)j~g{- zW~L|u8M5elqipgFFrV6*T7Y>T&(lc(3xNK-li5*u9EKM5Edqx9ExZbGIoEULfEU#g zxu~x7g)NW0f|bO=iUHtf^7Qj24d;KQH%!`qe!zb1H4Qo^;$zQX6-rb+??==#-$CK_ zC2W#vRLFK@Y|UXVbnCQDPISc_t^|$C6W?s@{>BzA;WXBWpl6DIte9;o0G^xFtB^ym zff9^CqZ$RjkSZ<(JY3A>lVNVqV9F<5l|0Y=4W57O(zr>o4TxT?C2W)c2!#NHz9YmX znXKJX%g&$sT+_YLh&B0(>!?*oTmlcdCGlZnNtT;^X3Hgfd9;d=@-Ypc0mdtxgfyLgD!nE&T-2C62 z+oz;=g4}+Rd8%`J>>7m?90q4v^)0!W#()W9LDVdf=R>}BfF~v$KI^;X*qGU9hifX4 z8L)_mTX4Y4;d|^)8jaLeG^x6hq>o9vUg+MIFpYy0}>y@K_|VJoA%zs7a{de?MzL*KSJM zTAL~gd&P&`#EB!FQ&s(76M$`O3T&P zuIYXR26RHw8SkpYWyNB-L%C`F5~3%JdOgln!ydE?-^2f67bM2(j4u430_JZh*hQjt z2(7m8sdEsN1XCc&?NpJeQ6b7>O-76V`wBrL9gqUBBF_D}w@A^ivGspUH9yJ_j4x7T zJ4CaD?wSMj4!>LXcvd_b%$P z&?YX#@wVLJZZ{>DMcT-(FmXon!2}z4!H4g2@UW0w=CGX|n-~Rh64y9Qv;-G|yqg^9 zpJ$c!EJSL#nL>&ZYvW0l@uIpEY-eA6PTHGAEmHsr#JqGhB~?YRnMRK1rQ6Qwp;GI8 z98^ve30YGT6Vai>@TY2nBBbVG1TeLj*01tWpb=Zd7LMZQZf_}UJ*=Y;f#?w~C*u_) zfL895Q!~EuFr zG6p^;k)6*RLSC#_;2~>t1b)j&Fv23?=nVYJ{sV`3>PD942(G3iNWjy}mDb%zry>fI z3rwlkc@R-kYmU&D8&q8{-2$o@<5TbGviqY;$emJ!j4#L;!eRZeDkR$1bw$hxktyR_{Wy@vJ-^*_m{opCkrZ+cQ1khXv4qo<4L#bBGH|{;foJOMqok@kLr-=6M}C;1)C^2X)y2YXcgu)SLZPZUbvMY;An2) z%)Vy$;G7f`q>2T-_>g7y5YPW~fl5j}12oK5Z7`BKFB##y)u2_vXdX~TCHuE#TQmli z5rrr9?!lgtny_Mna1<4h^MVjPVP|F{b{-T0iFQ(0A@TOPhQaisW2teprJzou z0ig#&f^087Oji7j!pCC1b`m7~CK`Hv=407A#>r_A89uWb<(#}(PkEt6shviMgnKRy z$YJcC&D~GTzjkie18hO3cCBjFupehh=^ixveGURNYEI3gcd|l_`76VG*5UKVMB5S& znMv_S(*;#I1jIQk9p2mIqilRl6=04?v=q5PB<&RD;$2UY)dIRvX zI&?>yu>3)5fz6Y11wY=wZ0+^XiS)$mfope|&aG;i!LW${>ZhA7*bYy;$~*ry{EiMF zd&Rpk0_h4IM2uahj=K}1>TiA0TXLhJQjfrJVQilbKB@I3V@qNa&c|S$$u1RLe)Wdy zxFZ++2?TzL7|>t;6??_mk-DcPfH9|e*c@3{YrBAe@Db(@fgu=HZ2JZ@<9U)~Yb!J> z7)$Y6%h79igeD7}HOCPDt)B+Cre5;r-Lr|#hzZx2IY;bo&Y@);kLXg0N@t#U0lYn* z*c%EY^V4DW2lAaH%^OR>oI?FO?b{1aKZm@fU9?*4_cz#z_qY67_y&I(8oD92jB|_Q zwHo9w6gGh`bokE5AM8GnnG;S$y0_Mu2(ytvw0M5Hg?u;;@_foV$n)c{AL6ka(>jI3 z2z7E@vVRs*CcGSyZ)1oJ=V=M?k1_pXetIov@V*duwFTGe;D`k7l-%`Z=CTbftvxR~ zxHVyAj;)sy%D)}RbEzZ)&29yS+4LwclF>#%?&TO$BydUlCd+4DQ#~BpvO;@jYgZP7 zK~9jBZ-iUS-9w(JEgRZ2Czf_a2=%9$GxSF5k_i_LDpcUR_U1$9OTS&$!Ikdtd^CZT zeyYJtE1i$VhXGAb(@Cc#@~2i{(XT?s(kp$NxwQ^-_5r_l9=yvk_=}dnvaMpZB+js91~~9 zht*`zLmclH0tf^UXD2>4A8*6z{FO%YS~b=%Tr3w!Rq zoc=7CrPX0l#y61LBd5Jxk#cm`C8^_$nsmQ*1nI%u$K8h;<`>LMKCOcDVJ4_h<%$iQ zgMYpa?(s$Q_fK7hz3XmPP4JK1?}UsX-RHChG4wJ^iB9}qD;sAyY?u9-{jpHmSbx1! zq{h*~A@nPl?&WL+#$k7AK*~{9`A7=Dd!w*|`Am@jnt=^yi@UlELF(Fe>Pfm!vi2U} z38!F{Z0OluWatjV$x_RjRY=lA6y2yD)BO~yFJL8ecFtQ!AxX?ocSHl{JlH3-xWs4P ze$PEn|7C&dX?SNCFS!;T?;m=?g%uSw-i0!IQVa032JWVZWV<{bFdDhK8Upe-{}E4# zXX7hcb7eiUH_%5Jm!H@eZn7pweMm>lvpoaXC5u*B&<;{sDnujCOdk!55@@A?6DSfh z5)6#5;6t8!r%Xh?E8CAiSG3Uq8pU=J-+*x`0TVn2;)it4#Aq=2(9uR@Fars2mv}w= zzX`LYA`V^W-T}Pht9pixhw8=nJG#(GMeHJzO?A&ouUb_|dNd?>7A9k-(7J{EX5hc4 z&JRR1-(#@Uo0OzfB-4Xb3?ruQw(H`tGe07X{OE;hz2`IK%A4YKIJ=n=%}A+1|EiO) zs4u@WQMeWVRx|U3;OmCI7q;h0Q%Q?ZO_VuJ7r@AKwmrtl2@E-_Bu$2#rcWz!A{eA; zP%>m37?5wZIT@tuW^3*~CTW3^>>?^G#FJP?)A+JwjKkqBrqWiT`%JVqkJue<@tTjGpr+UX17mwp+IHEC}uJtj7H^M zW{v6*;bzZw49@t^AtR~p(aOQAgtaja66Z$4v9C+MW2JJTY0}(2K#4v{I3vvqFD8Y$ z^&Qpmya;e!9+0VtL;5c+cl(Yv3DD})RB6u*l?I;eykrb+V0FoaZ)Jy(qXe|n*#mzQ z>L`+Zvj9Frcd94daFRF_8{47aqrc)n&*gJpkoc-ecb z{__X$TS)$D2*HjikKb$wdKR~EWW$TUQ*P+C<`{45*-e5$6piDO5lO(|Oh)>ltiF?b zUi3s#k5AT59|TKmy z6Wc?mmWB`xlfGU{i>3&fu$SJ#sDF>f!cwV+rufFsbKpzMiee#gO4$@XJot#knmof; zDi_i;kY&pqpcUcMRCkIdFd}^4O5BjBY~Ud1iI+UGd0Luu$s1rVgEl1U`n(hOH*fM1 z!Q%mw;j`3kH$hrB6<_EW8wghlkVsSrAz3p()#Kz#h-j>7mSg(0&?3PKBo5v^g1GC? z5JBO$2zFhtCl-Jdd*eVJ4hC|hgq~MKH0RqVDNQzqQjd*rm<&I+FrK*EHtKYcA1%Iz z_I$2m=#fyI;+dr@B`W}{esd7Mjhlex~P-p_Y!g&H*@@~)#?Z` zwe?>*74VXM0=eln%WYfklmNf^M-ca z=)*la92t13y63(g!OH3%#(IBr;S#mxE6mI^hXQ$Tay#{m{auf{S2hF=yjgNc{DVFb zMT3jc1dwJYH;*OV(+0C%u7}RIRnDYRYV)53MN^G?18YaPC|*Qg`(7}MtMUf&GfepZ zlHI`D2rJ)lTW*A61g#OIfVUXgG)U(w0a^c!9)pW z27doPm3Rbpylu&gM$l=_|DYFLSW=OHUi{6t9GD~)#erU4JqsfYBOEJSJ!IZh2F5kt zC}HslsUa?Q#6Ly>!m|(?gzj0=&g7(Hd83XVY#*5%Zbq%WL7vL7|> zC=OOrRGE$)DM?vyPDXlZO?k^i94Q!b@;znbNjplfCHK}C%wpJ}&|KGuQqb**?J_5% z2*nooVQV5EL2R6~$+sRy3zy^Xk-#tAxa-;9eYY!6jBB~12j)`hNhC;^-K5b{Xmr=c$>rsGQ{Duq8` zs-oyDPWogdCQ@jf4%0eE(aWmp^`j2HxY#^3FjsD2h8 zf~wg9S-^rk@4qLRC>=8HPJ9T5U}7p7<(+pt!gm)86henYM1;Q zBLfj-+uH_A@g(!?syf|ytZ5!x5jSn~KdUA4521#we((Hw8tW(*{Zo#-zTYIPDhUIB z5DMe3TM!Amo#N?snrRan&^2oON|=iPR^1+7JUzz%Uzf@U$K|ota}w9R%*)uD*$a4K z{>9hpaeJywKL{K-FfcF>5$TQmX~ELL^EGUN8E6Jf<7EZg0wrq&0Oq0sTex!YF4*|u zH6HemGw=}3bogPJFy>B@r|Jxrx!PV~>q;_727A@Dw(hCspuH2H%y#XkK-Gbx^fD9B zWlkDdvWIl;V$@!ue&KeI(FYuuP&{j=ih!=7RiJ^}qpnJ)mYAiM-n(KCJ=;Aj%SN%! zx*p(67Z*Lm=-&QdqDW}mNR&Q*kS9f{K+&daGMp0SH*@ODfa8BfNw%2G5te#3+)qB* z;lWcHSkb6QcOsVB)sa}&#DHa=VdS`~0{YOVbrkzB+GxmPm`$EAC;lX-KjvIYep2gsctO~4RgMmzj?!8Wb8D& zKemFHMg)(v;o51!WB2Of@Y;;d+Z2A%#fsJHP03tdg%itR&8+8%Mp(W#?{;sv`?&st zLqI_1&@bU#I2plMD3xZXu{pu3O#nRjrOT3yqA3&C`drsT3KM9emA)O7&EOzB3=tuI2By2^= zYBUnoC+H3E-CF>$_h^BHY<+z~D}MEP^O%*2oWCx{p|i7{NP5+0ArIYfj=txR4d`7e zinXnO$6>dRDWERS#4-yy+2$Zpnco%fk6Gk<&+irS1$fvxS*Vvo1|pQdNC?{-TbtI| z?vPS^EqY1JJBa(f#RFR(@1*^l%n$J6)OTu3vAbXizTHkgiE>eq|8EOFX~25HUVUg( zxaBQmWAj9GfL#2B;9iKWVp2f&qWKX{sT3;18pOlpb4E2xDjh`@Af{IhN{j&`ebF z8RIZ+{4t<*ckZ_QY)-Z1Sp{6_0JJE;-n*d#U~7hytiNm6SaaC=4JO%{q)F`mPdqW< zz$bk;DD2ZehNk|;0!-~|R0ZhY-o6JRdK2fzCR_#;Btq=C7lR8RVi_~TfnL5KWyZiY z{tVm#w*yu#egysrQ4OFK!*P9wDs8X@)626Lao`EpH<1;Ls3`)SOTRZ}*3z*p*1}`D zvL6wb4OxB8hWg)kibYjVWR&;dx5_yw9l(K0QXyS!%Mm@TUbTqFf4|LCapm&|Shw1m zD;8ee(eiJh;ooxhGcwXAG(ej)JR!ncPx?vM(P}rJYdSm$xrvw@9E@?ZE}KS$3+j&@ zlnv!pN>b!nU=~An;y4Nz1Sc4Icy*O18j8Ax;X@tT7*b6cAj$~;``L+bJI@6<5}K~R z@~TQl`EpIVL4OyVrpa5N-vYA5%!zSR2-zw`CvuvQ4`G5chF5izwP44_pYD)`MgVO9 z%-is?hlog!TbD$yWh1CFm^BdCY4RpGjgkTS3%mhEI64wmJDM zMd6`&k&SUu9FrFdg5Bkdt_2w~^9$v$EjwODLT-y2)-n14b$-opqr%D%Hw1nxcHdbX zS%tmQ^&xffw$bfm&!b$*xaavXA%5QLMxk+0_gbH~Ha!bF zcvwhR`&!s)yxP2ox1`7&aalsVXsI(RiOJtm(vN59^4lQ4kgKs;yTmJR1NI;xL?Hsg z#GHjYKzc*U?-JyaB_*N{hKCZYkkGwcxaBvLiFx|Sge;*h+w)zFGfYjquqOuu<)?Xt zR=`;6O2$>9O2V7o5n1#s5M;)XQ;7gHMCzW70PCjuub8fk>s z^HQ!+sPRxdAj^cHDx1b-YIZH}MC{{(c9zrKeR(`HcpdJ_fC{p+bq28*aS{f>D^J&C?Erp8qtJl6VY2_h6)Ob)Bn!A z5Q6 zS>!N$qJ3i13v2!Z=+KWG@68#5)} zL^38;LOvEc&?1;%$bl$mdn4JzJc6T4LN9!UH?M0?4*4gtk?Dg4s+7#Z?EEGAlLqcS z61?PKN?y%}yhkX$Z{Hj!N5R=ch%K1{Rm3yiy0>oLKc5xQ0?@IorDcS^E8^pS(T+H> zWV*K;w>H|DEi!xjmfO_hbSosom5l)(zqg%GL-((z18qdiH{-9 zQoa3AeFP?w;WHmvF0?COYNjvWH&=Z6D5D{*ZeO9Joyb%U6$THPiv+*>9s6%F{^MxZ zs8r3_?pGO)Y~R&X6qPe0z|dv52Ubc`Rr<|B_Fd5Q`G>?&OGF=_$h9$EjqBhYB7}&d zSF%H{(q@*W`{xkmWo?Dmf;Lh0h1+Fg8>L|kMi)ljXnFm8z$>}C^~O!y2j6Zz7*pMk zmbMMI{FdQlI3aItwD@J}g)ENEk%9_YX$vr>T`^)pN8;B~Da-Dd)9FU3W@EQ)$%_J# zVZV};A}NURA-h|Dktf#sMf9!&N>FIp)`>73+`M3foPuTYKDZg~A-=*f?9*oSiysx| zzYFU`R+-%5JdtyH?>2a^59?71ypL~8J|}kYTWO1ZT5~*axKLJ3bS3cI`@Opr1EDH< zPil0Q)t~PuT~FpoAIeNk=BT9<25C~VAUORlhNf!wEl20~xBV&C6dYXrko|0G8#DD2 z*|P0Lme5t5*d5)>fyq50{{Tf{pM&11|A3v`P`M^FYOMsw6zJ8oHDV3!WC!3n9TMoL ze-!`T`A;3P#pvbpfBLMHNl|ivRfp!riyz3nj}R}L!<6arSLD0@Tvn?AISgiIJ`h7X zRPESq0V}B4w`Ol79<+AK$&$tkv-#_WDM1L7*Dag5SK#Xre9>rsc>a@D5nQbO13$ZY zUP>XD**X-wu?vb7;Y|TG!+tZXxtQiIFItU1d#1`EdQ^rF_Z6Fg1rb9D-gc2=*EWy- z7h113W!aNE4%EGHgJC8waB+W%aVFdYMgDt;l?S9^q;l6bjJ7LGv zg)suhJ8HO!|F}b^?7$Z)$6gzreUF9R28^z0DQ_x{ZH=xK#+s*lX^G0C(La8xTvQa` zc3eX0=EU1mhEEL-zeVVuW#5rA(?uTpdaM`=sTOVSmOnOQWj;zXa)pYG5x>=BYz-m+ z+s-MNNS|$xuCqkBL=YA(v3Z@oJVpUCHm2-XM09?omHQ*Krvg5{-sl5aY z#71+Pq%bCgzDFTZIi0s8nyIjanJ5%@-8lYA$-mxhuV1yRW`3eXvNUk^MA$UM8NeL) z+(O?{Ljju3^V=(!pqFoj>MKE}h^Lr?lb5eI<{4ELez#R%mSmtw$=e4OYo%E_Keca* zA!9V;-|V*TGy3UsRkmT}#iFs&oq#~wb)ZT7?MGrr^9R6slyq%yDO8i;sgNSH&^J;2 zmY=4r`Qtdiv}}1S5C(HiBYO;oz_*|@`97rBWb}BLocqsA? zhOs6#(JQ)p=U(jrX!s8PDpgJIhBC8)^Se2RBRg@gVX2_PL9CGDsn&D%+wdL;7rw_UwRvct$JXROol#HW=GA8Gj@7h6CTE-@Rr2 z>?#OW+^Ul%dArJp`7D#uv5iX0F6PqDKvy2_T*tsBo?*Pn(;K{+fd%KR{MewX#1bz~ z3|pvd=Qr3c#~;crdWLsC=KR%%V3uEJ0%;_O;G}j5_WFTvua@Z_oZ(OQk2bSM+C&K+ z`7dZpN?1`M!-49X57;!0*LaEB43?bXxzWPTY+X%NeI>N(Oa1` z7miQniIIJ3DPMA%hR1tU?>@VQ7_Ag`I5We31Chrn=sAqtL#%=|0$?Yyo1>$W?lp~+aZMG3Dz27FV;;Cp7wV5|>- zMSh>Z9|iJCoA`t?i(8sq2EpZ;i03*cc^~=FFX1=Dt84W zba5+E^*FpZdec~;%$p)|oG1X>6uVN7#<8mNaAmcX?hNRx{`#xXM@M1p`ITjY*aCY! zR0oPQu(w*_f@A=&uYX95FCL0Dy#o@gqjil?5Rm`t65PW2M${{DXnS>VwL>i7pDV*N zyHKf?U;$sE)InE&@@@xu9k|G_SGPPWwR{A0A7udif< zFr9oy5LVI8p%+d?Vgrp;V|)1ELt?enrm&Yal|@=vf+dM6!t=bSs+h3+gvNy*neuHC zzoFN^yC09{N_f=Vi?0l%DR+mDtMK0`Y8T_BXC%R^3{#dD5GE-M z(5JkX65n~AeE7Go8Gg+vEOD+yjtHU)Jr$^EbRKF9?Z$+jVkgbQdRX2Br*5Lz20`=( z3mONTdFM6;E9DqSv*%Bm>*JG5Acy=e=uXc^bxu%3HS9Vrt*h|TO|y2dPPXf4Zr-An zx`ag9f(!rXpnNB{DOUCIFwpy|xjahdolq781-lNwjwpJ^u4w?!kq+n~A*ZfIIECWs z&XyUUzU1@(v=rPQ`JrN`OTCl*YA4hEKdd0@`Wt9iPP#KDMdS!Ll*hQ48}QTvYzS}s z`x(fQt&fYoI+wT~0NDlK$S~Z%P3PFno@R7&B{jH3Zy7PZ{yn{O-6qg2wayL!8&04X z$dS!A0M)0jE-tq!-pjyHRw=uMpk-Qbk+kems7OkCQj377^~;RSf|T(e zbXC}D*Ak49e~igDVcmlX+tdd9+v= z-g+G_wYXN&@!7OkIAIXC-oh1`I`6a5C+Dva{DZ8aG2B&D~;8jEEj2S-ab{dDw`Yk&A?drTql&mC_PHg-&fKadMoni z`<}DLGPW@62^K7#V!v?___u!j1al_Cn`T!+L81rMj`T6Vz&$BG@TIJAF0zFsxJBke zRY%c^TaBzze~3K%v3(>HjR#Kr` zh@(ctP8WjGkicL!2H38-;?#aN4+z-(NX-+0(esf>l#cD5&V{VZ`T_;8QuXgZbLP}; zSm|VV+AZHmnOrclYg?*Dvq;S+0}&m5F+eOb^yH)*%u?Dg@JRD70gn!#HP-V<^d6cu zY^pg>{@p%FPaR#n4zL;b3jjGl#=q>vmS~5%?C~oj2HL7*H>#r|vNRr zgTv7`lpe)eGjoW!+fND<$VIy^tm^4a%mKNC>G%5XrD%Q%0e$nkHkkF#?8K#ltTf%h zWb*lJcLp=j%u&$^6c^*}Yt^IN_9h5F#I_bXk&OE4+3-t}GrCGhXYLK$-KpvOWEn`A zM|Iw3iy>$E`Xa_0K{opDmxA`=o@>5Bmq?j=tanvTB4;M2dVeu>+K~~wB?F(U%hKby z$oFY0)!7-rgh$MR#G2H@&s+U&QK2Iy)}2DXws=?E#8f8{VILK~d%n;M!VDs%x*A#~ zcoyEbJQa+d#RrKiaqxpRDpR|*sD%i^nIj;^w4+mLxWvmI#MK@uGa4zBxLNsh%=@0? z?U|mRA%qlv(zyo2841SfjFs4Sd2FI)+}Y;q^U6SkWp|@JnBTcYgfBvTcO_I7m&-5$nEW6PMl0Y zXs;Bnc8?BtH2BANf4_Dm8W>$gUi>0G(So-;a~3~2gwQ2~j|)3WH>!$!DJn%nsssmP zy!k@1b75pSVSi)L9@Kx(8d%3uP20h!d*VDlx-JubvH3{M#9?nyoPhdv=rixV`Y=N= zB7eJ$7mu(W^60VIN@)hikK0GrxVWqkaJA$p)q1vs6k}_XOUC zlTlIqbenJ@AeH52g`QzzA_Dsvvk)kscpdR+<}U#~Sj6r}(+5T$P$Jrg$xeTpp; z98?k-WecS8jaBc%4V|05)EyBur5E~sxN&@Oj9qx2(S4V5bDeru^wpJ1S5)M4KG-(l~rnAX~-DZS1@()&Eo}QVDK(d z-1H$s@N;v5p3?{(Y%-}sU*0xPG$F2}?W4wTvo*LJ7Mf1pF`eYqbd>bkD^GygzyZp8vTLjGN+{vc z%|b9W@DzDm(B13PyZsD#L{^^Zbn=7nkg&VV9UX-h2S6)TyCpy1{dQH88E1&6<-a)G z%k&uG624amVK2x+m!82HYOk$FQA>MA!0JJYaS2bk6%YGRuvNcp9kO7R$RU3`0sY?~ z6*BGtdM^b|i2ixT(^+t2Um|%09&Q0x9!f-<&P7wCJk;D$`!(B<*p`_0;bP^Jn_#Lg~#<1VFgMNN8 zuAoE0a9Nb%sfKKf)?(#qs}y`=p{6RyY;y zMp-C9I(mrZo$4)&MTQU7iF?vQ58KkDOAS*RmMSW$ndfUR4^$m^ML)GV?GU*!ctasF zl0_ODmsX3~h?8<1Zty1Rn|oM9T{3I%0FaJ8l(@@EL4WjBoU6a~`cho{L$XNu42IYX znwFEBZTP{mb}OzoB#I=nhbEYMEs#!brUBjYt#vf%NJ7-1&chP*sOn1}1i_IEkm_wZ zPI&<6@~>8%tc^WuKouM{U79|DOeNA|_AExp(EeJwC)LL`F}n(H&pJ;kSRo1pD>7Le zLNd>E2aQ2D=x!eMzik{21k{jSOc}}MKzVk}AA^@wMo?s##>|=Yd6>QIirU;LXbdbYFxbndWNL&kd!ESi7)uE;Mrju{In%;{#%F za`yG*do?;VY);Szq*x&~&OO)aIi_Y03+=B2oLi!(Lv?qIhd$$tEr&*06oDM7mgp?! zIbY_TBFpdtn^>R#N4bRKgEc!uBx@Y~=c_=|>vkc-R0;fYg2GBa$m&MMc==+KwwgJp zqpmEe1vDz~2ZW=~=qXo10%*n$CwB!y){S*=B6OhyoP-@xvT+t^bojfwuPvAycUq>1 zU#1qXE91;YrTYfr9dKwv2V3%SM42_5Ly3+{nbs53LK5L9iELhSBDqr4W*!nSRpk4r z@|xi@$H><#{Zd)Bj%8zs*v+@XwtizFiE1+^e-Uzso_py88mxcYO7lKXrcFK6aBYGJ z8GXY4209d^ov&hF{_!d(D#x^jlEKKn6-MJwkvb+X|MFjOtZ>x{h0B~0EB337LWpQy zeGGyT+6IUFuAOf>1nkVOwjJDS`UXYxR)~|1!{{fGyVfybZ8er%;doAF7IL|G3di%7v)P6{*sdw-Lqn>W{psyl9`xlw7cLRZ^9ctZf2Vl zVNtj9$Vo7yhcw|Zr?Eb6pK|Ml6?)3~gb$))IzHb=?@@n^jPvpAF)N^R6Y2VWEtF~^ zI7luW=$=7Ij*;93%gu1I9)QsoIMb;QxtmM(LDi^J9zBi;d5zmAcc>)7Eu(TQKp@19 zo3$9Iv>(fSLut~Uc9?H7Eibih0aY&oY=7|+Rjf{LS&KgIPwo0WlfwLeGBVjKV@EV# zCUPpXqQ&BkJU%h5+=k{3#=GKAE!Acah)pz0zFi?5%(32C;aATuNX&*VGa*%&Hmo5D z#&1fHG5hO?sP(U2Jk7GhQZI#Z8p%B9C>MNctH4s#kTUci#e`979iN1rU|i_cLSlnC z)A>y{>Thc3DjsoL6g%Vi*rpgUequ=8gs}P%Z#bf>4wS{zj1j`Rq@CHv5(R|Dw}z^4KU%!dz~~e z2@&vWfjBIJRsx(4EsRHz+8$K~#?+!z=J=1rIN%kle=(WiQ{Bmy{!*(wq6Rt*cI)=A zpvklj$cjlMq+&dJtuj8PYtT_(M4zhL*>fkHTlm;5Om2S0DP0i-dX?!*6k~>f7h2Jq zj0U~(W6%X6es%>(Vhhj1-HB3xMfkHD(jM{~%>ZN8qphfj} zN3c~ulUT2tb{SE|KA%~9If7r#?wF2wM2irX-8e%YB5uPCNq)V117g}RF2R!&ul@-6-b|0 z6~$-ABqe#=;C^M@!B>0RDk5AG8`jFW0@|SNdSY9&ZV_11ZPb*|KK;^MWu;0!GC0M% z@E^4N_y^lEKbj6z?KisU_9}ASTS}78QGJ!?O^ymEDpjMg2T_I}N*`9iI}_@T7=eMZ zSG9ZM04NL`K73_fWtLDb9rz9L!hX61_GGYzS3`SVT67F+#CbV>5ncN+KNW2i5~-u* zw`V4thgx>wT>baiCugyZX<7&~#ng2at}QTBh)~T@jxv{wek1v2A|4&$#uw0fR@p8M zpt*v=yZKnkBFuHz)+j+eInuA4yK7Hx89?A3KifgV{vLX zn;KFQQoNQOykY_J94(zeIah@2!d8Vx$olL4n6Umb(j_-2D|LPInRxbmye~a#8)pr?A&kVuM$>{UzW8dA2&a*Qv`w z{fb5(MPQt$FX__62UJSZw=Me*IBmI$t*DJ`$4v`$E7yIlORnMC@!_)^&AuAIUeo@q zPBN&NGUxY>nhjxj-{-bVbm1H3uWBbHI_x|`hEJ9~4W7H-Gn-2Q%Lre3V2|%4C7KIqLxc?u7ffq8ee3zV>Basq2_gF82Trhbp#r-A@IXzkRLwP~C0}1`< zF*koS%0`7X_hug1p)58eVGD#Ta38|KxH?F{cNyo0IaH4c9Hk%EkY!}6fwp^_rD7P- znm&7iX=CFCtJsmhUla8t{3i_C^ecUx@5v8r%Ep{1WJwU}KH)=SjVnvQR8T8Kl!vSI zQp1l9(6STWk1E`{SODr11I?maGE6*4oU)5384N_K#FFi+)qMP$&_DoHzj3lL%uQ#I zUM&n{lo#E~C|BO5LO;nezymOcu>b%I!mN12qfvrL+~2&jQ!UHHJm%NUbb?$X*8dF7 zo=y@`tw>(>ui^|@6|O7^Kr~uKGIt?-=5`MEOu+)w1a&ZxcL~4gFKo_T)v_vea60*( z?Ng?!z-tyVO(VPxtx8zbL9tQ_>;`&&-p}+#k@CI)8E8j$^2MBLL0jb+d4im?15S$q z#+ucx|4AWwpYVgvE7C`|ax&w|tHr7*3M0JE>r3BKQECV3DAJ2#D;X5-1IVkpB14$i zMxPv@Z$;o4;*2e4q@P&qu6y@Z%q`_$bdSCR9OvYuu~*0|FXfAK3Z-hdxsGdzaQNoc}pPT2u5b61-CczrB@pV)jo@?QHDd zheN2CFuqFy8^uR@EM)~n|F8dy$Bcdf0fD5!B446&BAXB7@HPP(e}j=M@P>mKNNKmI zo8ZbN_6rv}$)S73Hc@_s4r@6HIskWWf99IMAjrIA#JLWJQA{F;FM$=oki}>^wL@aE z$coZ3fpY*6gygy8am&WJMA;x{h`KP0Ni$}3+9?VklZ*lYI{SzKBrp3FDRU?lixMB} ztSvDgb-QGo+2@{+Fb;G_+T)H$S!Q}Ae%k$L^DBxSPUBP7@gVRXlsMPvU%+QGw@0bOIgm0+R;LuIt2+Nc z&7GyBk1|b2AI+`N_)NC}cXrcrlizIv?iFOXf1l~trWTTD&O@boQ+xw)H*n%d0~xTL zZ7c#cV^a6&s7_q?{Co=_{m5ee3plVJ^^}8U1d^2Km*`{YZXD1KryTmQvxaPr7ivvqEZgRk* z)9Jxzu?yUhcs{oshoXdqG{r8;J%L?x%3<11Wnw7tPOm?r;2wrjMtNW!m$(&|lA)gI zpT`A?>Jr+uO|weRQima0eP&{LWrl-mov_t?e3DfkJ?zAJ;mZ z;La|4JFZ_GbC7Y(a>IpGpzQxV^H%U~L39Yi)!K4eUB;@aIBmBF1#ri5#M?lcd#Dl> za++cHqOL<~>^AVsXEf)3PR8se*7yew*j!z_p5|t}qESh3Is~zIMg*2`yF7!uJ>Wwl zdb2A2C&Z^2FUOTSDSB(Q22rlmOj-mZf$+*gRnuTS=>c3U;WEfgxM2b>M_^3KhLSq0 zxu5C%(+UPlV*lN`h-J1GY~neyS_2Xow3!oJ2$W(O}hL2HL)sA~OMtD=Ngmw6J#$kfw#QWOJ!j8mtn4WY0MB(@l z6s~8K>duME?1u}pTsw*X=vkMNANK!Qzv^bbVt8=b8nrZ|GWz3ig>K8LMEBzeWB{w&>|=KA{4<2hk{Hc$ zUM)B>qEHkCf@-Q4Hvw55VgaO@L6y*)icnvQO|5`E=c6B}VsIpMgXZA5`!d)}7`k0& z8;{{V+CT#A!^}X-3C9JMKW%Sp6Rln z9S@iMq#@c>VXIiN2e|+IBLDUmr3+|)kACGMQn*!i?)JCBGmNTZ{EW}!p1hCrgIiOk1qE9P z@h;v4=|B=^BYq0Phs>lg#PgTg2@LjShLFHgq;DS^VU9>ra%r-570kLk&!PX9NG|N{ zt>C!KaIO#3(o9>}N2{f8C!gg|Y(>GC21HqM=_iPL{`Fjqp?2W%CZdxr6VWf34z4QP zxK^Zg!}>s0L9TH~SmL=HUJ!;VS~(PWR6#8kh=Q^FqiP4CsTHZ$_h-iO*8uHvMTpB8 zhG5r|qwiW5Z?KSMqR$Q9Negy9|BcrlNGF#pFz1D5MAN24rDrJM>4F;h()&57oD>Yt zIQ?(bD}PS{1I=HxHesMqlw^bbph&=l9I#)Llm_;3{qVaeUp%nCVGvS91LcH$Pm|^t z;Pxn5*?Q#3+W?uoEaJiRM80%n?>&HzzKy9~xpL4mwkDw?ObI&uwGyA@ycT{;vS3dY zMT~eqxS;f(STH&_S{>X{T@v8_WsNI~TdrF567Pw{Eu*LKc8s=Hc%-kfb7s1#vY<^y zEfNkJDk8F(V;3+jSz#WGGQnkD04X0HS$3QdCtwoRhm`AOhfEDtkHuCI$r?7yY?qmF z?d|2<<@RlazwnL+`auxJM-QZInY8Y2{YnluJs83OimU!J*LPbMN~D-fq~nxga2R{jAuY5 zR{l2g8?vNDG?ju10XU*;;na4Fg=GjyX<669Fa3Jb4LE^v0DVWRHCe? z&Y{6M@=VeFBjEaakU9Cdp>ZiNsJTOY2~sbY;lvDpofXPiN%Y8xFfuvaAv-ZOKhgo56Gc{Ccgxsd{ zE^*&IJsC2*f0jmq-#q#wf#tf@UU0UFD&bK7EK1#K(M**yrHXtA&+%mpHC!DKG-8WG zoD0JK(;$eEckJ8KEe$2eT#Ss`r=6t%(I&oez#7`z&oqK18gtFn_}fB4M%#F2uJBGh z>e2jbh(87A$HIYVs*gfL{tN;}0tnQXN^33^#&j1N#pe8_Z*k^6!ri$dYUa#3MG-|S zVVJM$k0}Am z#)mYDG(cKJM!E|q{x_A-M6J2TwHd0*tE)}wM0?%17C#mR&rQ|hxP+KJ-?x-v zPOOKgcvf@v-Fs*RqsbF+HQvq%cGLX$K%CHqa6%xSLezU=@9*=n?+RDw8qs&F<`ajF zjni1@#`RSt2h9WB(&B2eOdK{Aq?uw$aA)vN5zV0&G&X+ZB=5x4vG}Gt(hx_Uid&g)QUvHC{!L!v3L`vyLDE)+L3Te-{R} zsaltahGyeu^gV^E<%0(RC8bK#fw34l&YukfJiobCE%`Cjy-P`CezytxNAbWTfG=PO z-YXzDlixhRc%cK99yO<|SI{t~oS79ej14rKgVKbrA`^Qbi7{)x-iU(-9aem2Rp!)P z%}5j@t52P*u71rDYPO5f)N2Q$Jx$9&fT}!&(2HKBTw|plNG5(s-Mt5q(3s#v_9vzh z7YtAKt-qIyg8E`fD+Trv^H6cAq~}0TB@OB`n4<{y5P&_8m|?Jn5Kqy&F!@k7WZU%U zWG!n2+IzbXzx$m`Wide_6bYfY|LPWJ??JUNhuew4vPf&8RoZQN)0WnBUyeI)1RP7U z;j?BjXo3n>LAxjsRJkv`&?)S!x6$0#FAH6FVd*=o=wLpOsa86eJKDlNF61I43I|{P zZ*HD%7UpAkrrSwN)zfx`ni%OF_(=AqjdPTjsYRAJs8^ zK5;+1e=6qofgX!rH{W_XvaiNEaMmrZ9UE~Gt|}LXsY7TABi4UgyL%QbW1;gL?r0&1 zdYxmv9S78i4(*UjmU!~EbPwZ?MbO_eBaPL@zOt1Riuh))|8oIY%+(FwjO8PEXKBj7 z(Ih_;&~O(pOSx4J;b6@lSpqwPe^}WoArKG@1rw5YIV)* zF>Db*7WMr%wq8_j2ziG-LPmo`IsbJI!P{{2JBZMnmkqA=u0;mrSWVYa8aaF#i#VlD zHuVM-L`LA8aTNoFiU)$vAcn9LZ%LhD^p}Jce zFw3xPBUBno{@Uk#%Y?(=5rF1;=d%?mJcku6uV<;Y&-H*{kJN7nJeQlzZL-F-95st= zlHb7FLm2c_;HKjpoWyrbK3QWE|btDPde~c7$$e7Awj`H za$nh2_h2}%nL@6jielow(Z|lsh75j36-*I5ewjHn@mvDQ<-Afkp?_ux#}-5w;!}|X zduFm+;ZewIj`9tD6>XEoG>Rq*k{NFG^eLMa4zzH`;KObSxOmk$=he&MqcM z)t;|L&Ux#@7(@wTsfg|B5t6FOn1FlHN25VT+e%|ZO7qvXKH5Q>)@YW4CZUe_p$+Mc zW>yA>$Y@Me2?g-;4RBAk0^z_kGJ6C5?9Z4m19N!{@6;yKl_xQ#`qYuw!R5K5Cc}z! zNsC#x7ISWNy~6AP*cVhu_qb$}!!lorUT`OB${n0W@zEpmXkO9GXOXoik#`tqGh@Ix z#&}nA97`+=K{EPflKh6N9U*im-;x`}!N~)_1OUoLOkND0{TrFp<_X8wL5tjvt$4y! zL?)?+Eq@~dt2_p&D4WU0aYZE--N=p@KNMf5IA2}VU)eR5*tUB8hjZ=yzWDOSiv)_W zhmKIBqX<9{M$J<`U>I(y8%@=HQTa&>b#(|7;C>EC>3g($oTS`fX@Pz{sA}P;9Zyd8 zE5(yZ!VpY_tm^%15Sp`*krz6!2H2S@D6g~Dpxzbox8Tl$>ZiYveu2sw7u~=EM`&@o zfc%6p%RugPm(6lcBZ(e3RK;p8X$BugkyNr3;JBYj$Gcw=YrR3YqA{LwN`-k7R#q5F zzZGWB_DQB#7>O_h*u!7u7r|B7h125;Leo26iN~_Gch_imiO_(&1dd!YLw$}AmU1rv z=nN?|T>c9yyXnMGB{GMRbz!*9=J^Xq5YP$W;+`}rJAlvlEdt4nuG!(?lG-o$rS_t| zLROwVtfy0DMC~9(a9uC85KY!03*19HxReA)Uo`nE5r1hlQsQg~UJFBhc$iaxYMbK| z;GsE9@|TZTr)`4QhF#W{*G)U`3XM-=ay{OEhR0JcimXlTL`>ejL-A*zYc<$Rl((p3 z#Q?4kHVkUqr6q9jlJahZP>|!6^okiNw#f~@bO@#FiSPW;F_&Wk3QyT!UXT_uoqt$! z<{YL5+b3-VL}n^D+m^xca~fTM159^c4G4O1wGTmG4*m zqyg#xI9F@^uGd8el|?`e<&190i6KfW@JljR@3N)F z!dI7sG0ly?kmf-+NA5Cfaz;zxRkZnXkHNh_KFG|GA2=J`r zlWUC|`glvdh&7QmGXedGa8}C9VqoDL9|wUDP~lbv`}K_?itH9q7)2$`@{9C^{_eAs z{iW#Zkma<|)_$C3J^`MA93P}0rQd-rCo}>pSozZ0tGBnU zwO5dy1Ko8)KxjF}V|rx!5GPrqBygqIu{%wX)v39BC<(Zhv%E_(mLD23a?4<8ql0qC9$4gEffV;M6JA*``NgZ*?;! zAOE_L-6DCZp?PJf)Dxxw=&NEiBreoGQ(x-TeE4sGflvn8>)dSw z(lzQW>^@dUmD$HuDC@m7iBH|_9~?`Zwi{U=9b$P}lO$B(%AK=1*RgJTxpoSuUoD++ z0{|Hp+}QdgbM+z^xuOPRhx;a&$J_k8!)Yfkyi&7H85C#Q5gC>hWL?APo)H|--Br28 z)5|E?28dI-p-J>TlYJ+aspjRw4}LW)(|v-Mr~uOy`c!*gued;s!4DyFGLP*Jrh*)a z2ub$G>SV4pjJqqOEIYe>?ecS*vp#N{?F@2#+3FhQY>{?y_cUOP(5OxjqyU;i6ZOak z-+kL_1P@aYg!}T|k9VDbXdAA|Dz}AdkP=`9Q%h z^*2^~M3yMAehFBU)^(4owpmW`O+jNTSOiakd#O(~;rV(PC@SYZU8=V}`zZzkE!;~b zajvnlh94LAd5ADj4lylhlW;J>X@sl29G;oAL!vG? z(SZfn6R#es*kJ{!-Ze+gVendP#tRuNB};JcTrUt?5(1l&O;!zHJ?Mjnt63$A&I;-l z-+!`p@35tJ^p}OM$ckbeH$cX4M}))OXKFdc5>}avVG_WO5HJ}`fcgnP2(#Kh*TWf$ z$KDv_wlAk>zqCSaly6ZvnDLy?CxH5BnNJPiV;Pp)G(Y=Og?EN71hlM_kf-T6#S1Kcm1qYk{syAHt0fkuu$L?dI%6z+1V?>3UYVxqMYmQcv))?6r( z11Nj6HA6>0a(?W0)jxUlzl7uhGS7-)D2@lGsW1vvuzOh*vSIAvn1#GEgBA6yJ5v$x zWH^xSW|&cIsL~J;LAu9M8iQuL2@a=_dL;jF1EWQ~NM)%=hT9QgZ1Kk1-zI0%QbZkW z1za~lz$GcRgvrhicFa31O}lX?ib2VMBb?$%OnTCzG%4V+QrpLfQB=8p8m1-%?98=v z*D9%)X`zW(w*^Z*=smP{l2V%4Szf&XU?^K`sDGJ#8zf<)BO7uL0n={T)!-#>TwI9! zfUcohHr%+J;@Gke>NGr|5Is^`INyROuW%5jB8B?{+d3quKs@}SOc|1yJ>~eLXUPe_ z2QLT)o*cpG|B*!jn5!s7WbHQ%4|H26uE6kg67p0MR3Ngb7in1SmVHXb)MRf!j z28?`Z^u}N;I#}U4X2Unu>7#EXHct2B@oo|Ly28L0j)VPpB7{yGLKzH+ms{GR0-3 z@8v1G=o$cl;%OUhcb-8*9OwPrN#mw*tXB&_;;J()DcZI22OeZiZAkMw*!es!(_joc zF9gXDmXoT`Y-ph3thu<~55IuD3J;<_;y$Er7gfmV2~=-*dx1Su zd$?6g{aii{%_YB;-w=k9!qMYk^~(jnihT>e-94zHVYek~N68yntw{5(7{MG_oV<^- zSXy;7KWDXUR%fsFUraKZb7KO*M4RjbiPAJSC})&Bf$^Nz1$>S1M!BLBxt*?wZ4k(r zdUGG(8{Jt+l%9%?uBiRckxc6FRpO^s+KVl0Gpnnj+=Ty?n=BCgpHnRdMu;sqT)%f5 zUM~9acm%taWx}xfl9+);H+3k*YT>e4vMH{Z_xVKx1;a`ZyBc0<^agCaC*I|#@Jn<@ zRsPZSNHgii+{x6FP8C7b;Z(ITJ=3@Hm9_gSQO~!vRR|NxgqMyKMz$69E)X#?XLOp) z#gs59U?mZ1FZBqua$&$Wj6HXw(=BzWbfRQ>0XD7dF8TT`z|9schld%f8gR!^k;aJJ z$jDw#rH}@@qh(KQ$NdU0Y%UB!g0uzEV1|%h)%a`#=~q}h-jQ4eD}ISGio=M_TmBx! zS!anJ$~|fZ#+czoZSflT`sb}%lXiWVmI@~fjrA3|qTC^e82cS0jIv&(C!9z{2cM*l zm@DqaJ>jhAc-x!!9!^G%Vfzz>$zpSLpU5oU!;bTW#Wa%M=ZfK;s!2V%wDxy# zzlEQzM1q)NLO)1`j$8!@SG-E-F zgjLFKz^T^XBxb;`G?)d70gLXhvF*Vt*UALVxma{fzy4;gp*6ab<${dgG5Y0rw4pNn3S+K0|Q% z|4=S6kK;%7>Xw-5xAu}c>&>0)LayAoa31!`7?Ip%W1WF&)+{4)cDm4x1M_G-KcK^t zo)?&DOH-G`<>YmoI`I1rCc>9+IJVFWU-&Cv`}37zA(b?^V97`%*E-9|k)BSs2%=D< zD@MO^TCd(F!&LG$4dicdSn8V?owYcHpdSk!xug&zLPYLz7u-jVn_KZlSE$6GzYEFaA-l)~{omoz zK6O*np4o4ht7;%-fpEt6`Mv#dF+f7yG$)>qW86Ro=%@Dq<+T!=XiHZlvmm70kR5Vj zLu1H$dh+bJxTD2SiBgx?FFC%lX%?vu4SX8#3aHgHao=lMK?&?T|0792F9!G@Jr_>+%{uMVvb%0_8plNTRDyq z^A*43-NCa~O|WC5htYaQd%Manf8hAV0t5b|k~f1y!N2`vjfkBgx(3>#9j4gW0vSFf z%!v?0Z9f3uIfigXm7fCYH=J`Q6 zDxf|54PWC7GbBZ-!pAGlbIM&P=2mN%gV-db*HZ0Vy6Q9!`h(P-uve09=K~Z_bp&QP zySY)&qD^gGch6d9&AHup33&+QDF9gr7n`eO_JieL+fAO0`#L-U@t7f&XPcFMQbgG; zb|GL395`0$ExvX;SHIGMt}mS1%C`n6ZBZlI;l(6gc1w^YpG1>Fs9T22@cPcZNeN}y zHfRD zpnWTf@2+a4f-nbc-Lmt>nsxzYGpx%4 zD929`s_F2cBRzdgLmB*H9OJiroH&9R7ebr9KIJyllkjEp@(0R!HfP~2!%o!)JUMid zYDg;fz3uy%b+csfOpr;FZUeHe&?-q3eTtTxp z)*oebSWT(*y30BPz4*{ zEKlcyt{tkr0ihmzFx*Hk3Iw3*7hhI0qb$irTH4fQoM+!oO7K|#aZHlX_i`Le#wZ)y zBebomogV@)--MIhU*x4zxCr+18pk>#Fa<-o<|07H^O^s51joF)mnjrH6_0&JN!d|= zefyXW^8&FikZr!5J#r2!+``B-?77vSCl*X!)lcFsszX#6;$OQ*BCU?L;+T!J=_b6l zL&RSVA9%eOW*6-X3(7NbsJ!HPj)7`j3o}z(LXxc}gORPZy30hW+d%_DW`F*=NERXC zTsV=HR*8i<68%RQ77u7W{K;^KsAiCTfi%TQ>B52aR;M+9yAxW%Zw zrz?K)uB;gxy03O^3!2773POLL%YXJt-rkcRqky4a0D!Z+&p%cX55vZ+Jn&)NEeSt| zYEJlkxD(8W#5e~m`l#RcPzJbBRcXoGx^X6$Jm|CllKXylQ#uFP{8vdvzAtuo*@o_V zm^-2|E>HF?D1+mb>7)*qIjz-n9piimUUlYjjOX+@)IP$>`+U7*?_nZ<<*?lgGUtHT zM;J*s5skt9r8;Hoy5s9Uo5#sL7IZ#wKFeQtam8GX!c!eQV?jY*lIEis)3c{cdHcBb-1X-gDlo#UnVZoAXk|-C$Ks%0s{Jq=M2Jdv z>me%R#aV`=TjH*j4gH}#X2$z9)*-AU``Sg~Mt4|Ow|>vcW)JL(eZ>V8G*GGESJyQP zDgHbRZN=(8nvdnY+Pdybl$zwE4HExp?8JXO0)mV&B$A83wXj`9>clHx=E`8Rfz6UK z3Z~z5-FDq7#-UiC>^>W-&FlyPFUicp!uP}dHfWh$;&ouLH(tb~zRVd)xYl7if07$> zkaC)$FR}jqA6&wav|?v5kj>^k)Yx zy(lfQBSk^b{2jraDmpN8%WMkW4M@CQ(sVhdycY(4rJoeBaC04}kJEeDQ1)#M22Y3D z!vKV62_D2{(-G2>tm#L3vYE`N@!mdTCA?}(^PHFP(ZOTo(X+JF;hY60#~|pK%gJ48 zm23-i6Vh{I6Ao{hVQv_&m3?eMVoZY22u3m3M8Shx;gT-MOhK%d=hm9U@%|G+jpB^E zMEJcW^DQ}AGS>4;&Ven7{sFu7T$M8PoXmX>9QK)_7DD?)x2zzaa5Dgt^Y&cM0Rc^;3=ABy6T&89nkrX$>Sf8paxVsoOm7dU|>6<21V~JKG~{nkvll z76W(r8y^0TUn9!x*hpLbD}W#6rA100eu<*3O;gsy1t$TG+q-f6vMK3|{Px`(N5xLD z9mhj!R2D<_ZW##E6sc5U(%?Bx(Ll6;MVm?+K0aM9Am(33=Ow3-k^RkpBiC^|h>Vlr z2cT@u-qFFWkZ7e0w0U+L`O>87$=96v?vlH%@n#X2zjr)i5K;qk$a07DUWPl>!}>%Z z3`!%$eN42oikRa_8O~_x|MB)`wfdJ|$BSR!L7^&Gy7TDEY051%fHKWe)~RSX!cohE zE(x3SrIZ1}+`IX{9;5%k4_N;3(|P=VXle#23j@Td2m{6TjLy zq5XVAcTpWzk20+)+bHG|zX4Yx^M-1^)kL>R0%~m8q*`zopDha z8U|g1onl@k{NAIRPX(v_;v{hHqBLSu3uy{vZ0XL;vqT@(`sgKHGwU-u>lp%?$v!KA z+t&5TnQLDJf^!gGFA!w!a)_=9RkNR?r1D^(n93Y=^7X}|j;#qfvFT99sv@cdahg`@ z84%6*y}M;?V?VMi9eQQSNbMqb{m(X71rVJD#sqI=HvZQoOtq+EMX$KD;ElGKCUrXi z<#g*DX;$1WpP&TVz@iW`)OIGF5xa|K(QfLJG4Z5Xj~)k0KI#Q`ziK>wr4^XjB2J1s$3tqxcTuxY7M* z(e-xW-`lxdn5OjMjHV~Rx%orB!D;~LgKdeKaCqj~bv-?cbG%RkfTaZxIF0y3z^)a$ zh)Gl$XLeG^nco$-^>qqWK<}ge{|7!{wQr58YXdpQ3G>yI3qeg3)}Td07`e#ZZ5JC7 zk?V{WS_zxt--2VijKk8K=Z`JIj#&e14h#VohL7Z>!0MLBF!Wrhi(qKQM)OMnX@C&r z?bGwxeR>skga=>PmwM6itoZjCm%A}1xQo8vO5yLOJNk=uP?Nv>=Cb>g>ra$|#K>E7 z&_{2|q1uD>62k#_m;;w=RjPWH=b^Ge8ii&6D)A)VZ7iro1dr5?8}W`*-k*;a<&o$E z*xLfx*^YM%bFh>$;u+y@Ms7xJd~COfD3~!7?#bP3XC#tx*Sx)jObA0YKYkG(1O zo*O+8%I`q9E~?|GYe4>|={n0%csd*Y@kLO9&d_TJ?29ntghkiAU+>brlWc0VcW5_OwCS913e=(fG2LHmTS{G#H%2?Fo#BR3SJ(G+`SY0p#%xuF!{s>p?=WH_}Q%GOLuK{Lizu&$(( zieW|(molJ6&ASz{h&mc$wD=DFhve++X>I`J{Bg!H8Mb)+4V2Zw z0u|>lmu)liRdM>ShXoaV<6$*7LVn4ADiQfj@$_ z=j(n^yRN#~JXhFBGk=_iq_Z{CAJ<$HtpZFHb8pE7)Iukg-tGY}M1I)_jf`!)XchIJ zN0o1y@6rbC0)or~WvDy8$S}E88(jzBtgu0M-Zo_bKS030VY!v4^>O)?woO)c{V$NXuEUuv3gHmFCF=uQ>e<3H))1S%)1QZ&D{HNk z^ylh3y_2V--ZLc5S4u6d8P6FwNd7ZOCP1{Oyhgw!4|CP0G5hGtw+pG@SD`vu&b2wl zpj_q9`GRW74-?}?eI31$M_eY=Ozm+s5|?xo`m)IdMa2mKiwXdA{7Sd}ALk`w-R-w@0P{cYQ&vg0bD=sN4&cxi zZEU$@4c@&+-|fi#5T#?tJ=iZGF4raW03s0ih_M)x5XB`As}S#xv_=qUE_s1;AZsJ( zqInhI&(A?G4vW?yhIj%Ah%jAgXD;paSFnhqi~0LhMic$IigaCjmURwFgdl}xL;F4E5 z*Bpa-U0BOvn-J=@&aAU@uY5KG%sUcO?C^o_*%$ccHkGa~WPI$MiaMKo)sY*|Fq)YD z9$Z%01nQJ+Pv8h)6jto@-8-xn-r#op&bL8(5a9NCpHsy{=PXnel&LKhb@_T0g-Q%H zU9|pcI5Kfpa|xYZ)@qWhjMWJb?wa=$a3kKir4@6^(ElQ!G0)yiWT>$cPMnHFLle}! z{{k;wc@MU%2cil~Tv5OhTgIpC(^RMs0^H)81n{N@PZo=Egy zuXs$)H@2#QN3W-#`o?+N_47UF+mp;_T%Ji!S2D$MhIjhQHr|h(zwxzPkK2tn?M1oK zjfnE#y>y2UA9E*Lg5vN*1KTp8PSt}zJWc7hcF_?TR0_s5I9b{aHSl$%1b7gGvR?}p zLIRP2e)%=pe7K8QpA_)?R`IU{ZwN&QmY){XkiJ zHG6*4ZVXpliknz&^a-y2qptO^;9}tJC)mb*W$^U2!z+}H!niA_dmPkbQbD*=0L3s|D@40Y zCZ>c;B$#azD zQaGa3j41x}Wl#%G7$$Y!Zs0y|czmn@$CltkaL>QqL}*qq2INE9U>#x#?%>NDkJW?~ zcx@QHwZRpXfNq+@(Qt;~>ID~`JR!$QSV&BI7@6a%;XwPMggASrx&Ri^3})@&!%XE= z-09O2_~1Qc)GL%})wehHl#rq96$CRbA8}df%K%3i6a;AVU3e zQ=a5dr(cGiOcXH3qzLtFCg%_-enhkgwf7Gf1=KKRC0D#+^bCqnNy)?)V-Bd})_wn@ z{SppMPTgSMOnq^?^S43CoYJgNdjXct#{>PKrKzpl2{yXr@bvsCV!#Z{$z#uCM2J$u z1W|!l>$pyNHA!Y)0$4vodlL|zbvP|Wi#688mCVtmtIo?$EINDf4Su2?kYMR9hMj<@_S|!MvRfU-Y&hh$y{`Ws! zoMiAEuwv+XL2Lgjxf*9z;~GX6usP-(5}RfM0jDPud;Q-P7S?1t%Ht6b#?OO+50Eaf z9RGk%6er0yYimQIXP31CO72}-O^dzIjiR&S;*ZJ>-ovye;XUmQK7IS4F54?hLd_@e zt)kO;AG9#V^x*?$K0b~e(>%^42HKytIkB|5r_5#oAy4>czADCu^n1(z)+tY<9+9oW zQ^mz=DS~>8Xf4P536xo>muA8i0`&4tU62Tq5nr#7)A=A92LR9G9`}_skMPF#%6=bG zSPUb9LB$|rd)gDy)|nSz{89s)NIFXS5?0djY?-W$BW)h;=rNaDF#E;MdcXs$vyw^( zU(8Y$#UpD!UE|;`Vj`u2sKRHaywrh8Z(34X2rHC&1Y&1twra<{xCePwoxB&CGAkG`9$R%b@%zF4sgu`ySAX>?wG>^@$R5G{6Cp%1Z0_=*_8H0y?z@SHG zfGY#ma+2SlW^nfE=BXTV=*QLId|jsF-XBw&w%MKH>6bW_>nW5;?9m~1{jLxOi#Bq-#SL+5qQ;vbsX-T+d3h~_l(oUx9!^}n zf=VUE4HgO%GQ_FJ3Q#stzBW9|eC|Z+@Wp;wgiIoP=84PjCX9J%Dr+BaA5^5DuIdw) z;a+|bxPdF%w`2BMc%zl^oPBGmOtZ6f3%|;sy0LZ9uX)KZyM+30bOal^=D$MuN$Rpb zqH>YIe-^-$UBQKXy5akHvCIpG#z*4{EZdY-uCvl=kiSBJAaWQ35|38;%3K3@Fou9Q zTk~$8`zyDPZhKGr+v}k*xx(p~7p{{IJcB6Fnz{_QP-I?tX3YedxIBnODB_&zT#^l$Gq?%tg15b4xGdX(gFT;LzI-id1tQZYS#VnA7SZY7w!QIl$FXb&FnDP zsR!GF(EKnb!ED9j7xU)lZ_QJuO-7^EvUS$sX+fye9YTQ(xp86W`Wnw5xqkT&{1OqCElci9!$mNu%#!66_Mp6Z2`H|m)^^fy4iX=@L~obe1%=i zjjJ|{MPD{|XV$KT4r5XTa!0zEBMq+j(FegLJ^&qD!c1Eq;wMdh7%kSBE)@_$JDf z)k!%28B_HxSDcz!MfKG=mfcQ=4~GRKuJ>l4 zcA*FtZ>Bd}3@Bg`#O+Q1yaQiAX?m2fuC2gc?1%7XyHTY_U>Tc5{klup7TQ*Zu5>bB zaJ#$+uKlu4EwFSVZd4tk%0wWP2o;J6FpEyrxX7YRZf_L;*-fmiUkmzxUw=Cc|mT~|$mh{Q{Y8TIpas;Uwa>*SqX zC=3k}3C!fmo(Rs*bV5&_1C<~{O?RWN1?LVo+dchOgd(X6sun=LW9#wz`cGct$?l57 zr9kG5|I%o#=?<_3Q--Dxr(w|J{4;pyC6m!NyTqaQvLm1tQKTytYqV&^RWu0AJ$~gk z=j9Gs?s8S>WW0G#XxK_J%u;Duk(vK=&AmaBqiPd2p~BWpMAAdVXYY$y@}@-W_28{0 zc5z-N3?^nOw&V~QxizWD7BB8iD4b>A4E;B=q2V(T|A*I#{HpC;8jY|#j54j+c^Vd` zN&`T?6WFgaozNqt%I$Y~z=SK9YZbuJ&B=Un(;UGGZQmi^*=z~}oGcca_~5?$4jFOv znokLEwh^qoxdQoHw$Eqyw0oM{d0-l#91`9B+sS1Fp--xzyUHj=dcsa?6JAJ`+tG*h7|5>Uo zEe+CP#Vw?Z4zpl~s+&R*`q7h3izC*-rvmwg(R2-MzkocUbfh!Lu1iDT z14a8d^Zu;RGFIHVNU5|pe)Vd#5&7hcUu}K5K-xI=(Y8B@vt)Y+j@a{W_%I3mafQ0b z+pr)D=@82EXC}sTaL9ryFn7baVK!TcWWN;VNb?40rV98lrmS$}9Q?$#bdpJ-D7GYy zMu-$y25A1ld@)K|>aYFTN}5}&Mhm!$Eq@pK%ijN%sB3>K?plIJv7PE) ztqV!xsq?)7sS}YDmFV0~)pO`a-QivR1T+5J)WRC#oq=6{`B4&VnHhF4DhmmkGFc9= z3;F-EM4e!A3HwA5i&;eA=Au@3H7`p)23$hW*mJ}P6cmLD$EHs=gjqxOP+AzS zc_=_F@%4AuJ|J^0+nzvISO)SZ8d0EA_3_fVa$$)3`xJ}q3KR7z|1H5qMrGTS8>ibY z2{0LEu^pc`!4RN$Mm=9Ug%UY#Xyz#N_iX;r8YsJjgghTakP6~kZYN9(bwJaUPh%7< z2pRW{uS@uP4Wjo^PO{+^1SxOTtbzlKlTjUGI$Daeb<}-<7aA{k`Z|Tvx%)v%#sf`x zvf4}QSP58GrnoCo=X-eS?n8yh+VLt=f* zyFL22&JHkzRZ`OElmQ)oViyN$CKFf9C@VRcX-IrrBZVrEv8_OpIFM&Yc5Ll1+_ZhI@p&A7t^Qi~z#B(c!AFEPkt zP)YO`tWobv)87O9LG~Q!wgtd76Uu02Hox0!Ai(-W#J&*xm-5 zOrpbUE&J|j_7_Eltf$buJ&!W5e&zV!u>V|hH)nd$K=(cBkH%*>ePGY!V6^VJm&`fZ z1e~>OE+xYbWXr8hGp?m#{TUWdMxloeR!1=IjiCuD3*kTX(yDx4dnLd|q%%5)1v8rn z5_bnp*e4R6-202Nr^St z*+|FFyzpV_khBRr*Hi3zF&D59+{X}E$`2S61s~hpInVH5$~hNUq_F27lC}}SV!N->XAYL70k5#%Kpyg+ z97E_jTrT)3Jqrff!&Wov2S*v>E|1jmjK#RdAlEo*CVwq4e!;JYnwdXSk9Z*+%ID*} z;CI|Nvx3M!Hy&L=-jHybfwwTri$;cHMBiW3vDa+2g1YJGac;^7u?Co}WCEKz8e zMC9CPr_n5ssV8knuE8QaBmt<_|G#<&u3U=pp=P;#{?cRR@r!sclQC|doX+GjbQ?2v zjB!<0QLSXu{+C)QWQ6Rll}&_)i>~FhHzT@2YQkV~lvO~*nd>4gkc~*jc#|*`L-~Q% z?C37{-+zjaf#b*Do ze~nt_6(&b|WvrN#u*>I-o$MX<5b?a?#wjvtn6KimLbe98rrTUaKIG}YfNw)?pL|#>Bu_wMuVkza%{VeK^a?fW`uiMr}EmJdHNM9C(hcTxnyO{Ns(gD+Ew=&(Zu+`Fh^ zQ)Jv(!qx2P?BU9AvcD{=Wh-er@%R)-@)vNNA$ap-#>-uzlGu%7Ukl7Z+Xb}8MIZKQrk6B|1% zZ|38nEaY&1P{D>vkgmKr_$#W0_$YTc(KN*zX=pDX5{LH(H^r~nq8iq^Y5+tro@!Dn zx{Gxza{rcJ`%JvW{E*143l*imPmXc!^bMlka`Q*z3(TA zjEx6M0C@t52yxO-rY;97sEdmgZm(ezW7s4Bjtg-A?1e!TceT)}E2lzX7`izjAXQ`n zEa?6*zAjH%Atj2BPKx}`O@=8HERXlqEBk5Ar*%^G4}2J+Zyz#qH8roh+?x9l$LBU|JQedUGp$wqDW)If9Wj84Bt@z>;lowv@|QetEGiG z9k3&FCIw z#Wm8S>|8ZnmlaqtSwZ`erw*!V;t?Ax@kAS%`6o`z7jeXNTi|zV0cqoMN-bgll7 zc#N!MtP=S0s34LdR1aF0ct|;NU=7^^+{bNIFc%kpy*%kgzA8#ho#Y2PXe?PZG7Mga z!M7>2MsQL~C53brUT*`(i)>7m26tz{PfQ1VZ4W#$mNpKQ75tLN7Bed56XH#dSHI#0 zfgaR-QLs63F`$ryE-cPL8KSf(4##JWsHTWQx-Ok^b=xD>1`|LoTcp339n7GJbSl8fBjV|IES#Js0F-jCpCr zCFbm=uW?mHx`8**^L0E-_wbK>i?OB|=;%ZsDJ?K+=f{rDMI`HfF6*+F+@Ko%ymX!D zyoro)Z0dtH$*(zrxJOTo9l!_3$LJwMYL%ZA1_pY2b=Z?l2mss0Gwvz#WsYYAl!MYW3l%z2%_rDbcgEyJQ&h^S=InWQ4)QdN%0}!cA+gOM*<3&Agqg(G9 zmX{*ig&-X3sH`3tVxC$XQ;7)Mju*2u&ruvs!fL;uR`@%?0SdB0$M`(k21-o>MauqL zT_rwDQgH^;xvD&KA0gAz3Z7j}Id*_4WA|DCsO8AT?I(Nx@B8!?l#DUifwhPE9hU)@ z!SL#(5JJ*-J`35*67Kxoe|M+#5qVjjkia>y>4Ks2977jq$GBBh6d9qzUm1vrgkg6Q zSiqx)0IjCCg7DN3efhC2qmq}>TP=B`k;Kzou1r*fmmPV{ZtP+ z$2-!1?3kwnlnyObUX&brOI7y01EoAVv3b0#%xA`in@cG-r|736eAObzJOTHM zzRpsoNTDg^HU}SE0p=R$iqLsi8=<=Xnc-ZA0ZSt$KWg|$I=>2rTSRzw~q zp`0ZU0(VskP|u9OXjQZY+^B}{$DO1;bE4#$kIRgKEdjeX8=L;0Y#M}I?@z|p!NC;d zL)Zb2_fjd`HNRC2E4p)UPHxw?N`t8pa4=&}nbr1Z2&ELm-g;8e>(N4Bo3j2l)AZO} z+jgif=Y14QebWDWbE9!xn&ku?sMMO{$dk;q0SlzBY-;|IDQSf!T6E!BN|cYl;+@p@ zHO|KDh3W=^7y|sF8`1k3FS-{x7l3I;tt!tko~{Z+X^TM0!hI{f_w^H%KKJ>%wsjI( zBWp_4@a%Nf7|Ur4H_d{&|8H=mf0YQuPZuKCYMttaO{KJ1D`$F*SqqytO!LV;IVA+E z_WtiFZ05SlWYeUZdcS$rZ=ZFN4-PFx=|m5F36un;FRBH6t)KRWpneeeg4|1jj$~{Dc7|+@2#386TbaK8JUxq{tGE3j_%M&Zu|e1QK^ED z(XAw=s7HrCH!EBlOo^Q84prGcKwo_rHyj=2q34iLThmkAptkB;M&E_bwzp>uIX)19 zZkyE^_!8_xta!ctoUSUaT#{QNDtde@5jZz{n?afebSqa(0*=?vxEi#L)n3Or;UO2G z?r-~W*vSU#Nsx__t{%;cc)rR7cUBYTc~gy}Xy}y4<*5>eV+=7HCG>)MzpMQ;xSO_< z?WN+fB#0%%J>o7XL96I%P0Qwu=R36sos8LN2H95@!Rf)vRz!;{zbI{h2koYtwDr{_ zfNmtUkm~KB2O?7DKv2=g$vy#e?Z@q}zn8t!BJU2p9=~Eb&FyGo%1{+)ZAMAz=x2gC z;Da}`EOTcOI-yOKS?O~m5agkJNxZbC&Pq;WSGuvodRUlcy(&qHOEg#hmFND9j4^GY z*VFi?3f<1(-V=vlz`v3mn%LHUZZ)G`mz#Es$)`61VZ%27P^}I|$9k>$MV9lnb!Fa# zc5i_KONjWuV3-9nFlBzK3k-RR53B;=&ppjh;}N(PvfLK~7va+M*9Oy$Wf5@=6GX25 z&GxQ?Ag_BaHA()mH-?ToCIfuZ1Oo~$JmZJjaMF_VCG7<^)N;SMye0a1FBaf{oD!~6 z;Y@m$d*C~q$&?)^O&1bRSQ{{eyI33k(|Utcd}?@~CQ1#Ox+e)Sz(r{lf=nkz1%g&} zdR)0Nl9p}O!ZIsm1UK1mejCVBr)a&<<|tunpjhbDJvS;M(f zSGKcJMU}$X&9YmTn$OA_I`v8hc-wMS-aR$%#U{4}V3;Pe-4gvYdU2o-a8 zMb_F3HiGUlLkhPmDTRAj#BG;HjOLJq0grms`8^mf#8;>v2+%*~)XzTuyqIi>1n$=Y z8_#l6Js}|*a0vBCNE5DRk(H6cS_<;9Ha}UKYj%7V>(qZ6d7~_^H3O3DZ1>5KqMwPR zolotB#G0E=a*PQkI_{{G+XTd-Rtc z=1RURw{A{RjWvu!zEfh>*G13`K+pcki^!6;@#qOBD0cW%={yO? z1SgTJOA^k@-TDcIMZ3#g$WK?GX--=D$Lz-rG(;t(Pt*{zu$zsTLGuvQsJdC^RM}E9 zadM)4eR^q~uMW!^0YA(bybX^atOIrhSKV%?0fUK4M|)>rGl5?xZHO@g%9_f8m64n( z64%Oq4kaGt5T zYBHhmNP29<`tFa>3Pi3)Ydo7fTgY>{?fItyvlFDTThd+01xpT?(HVPaZE ze-B`ccjjIXF{68hf^JiUwm45H-L5MU7r)s;7Lzj}r+m*Rcv>{s?>&L)Ar}OwDS`xs zDpj|;knXlIFBD6g-uNhl1_(KRl%2)}sS*otdUC7hq}M0H%Fs|Jw&|+;y9b^Zc_Yp$ zy!#EwU<}F1PP)63eeGw*WC88>*`Sk?H&CHhAzeB|k1JF|=xyY#9HV9J&)ziH7B_)2 z#Cg1g0kd{Xtb?YWf|f%`SE*6;UBrUV3rW7{SQ4~in|aQHV%Jlf-V)U;HKRAu%hGO^ zN(xf^n>^-DeGXksjuH@J1a_K$-l3CKj2qZTA4pK_YY15RP{JK_MMl<##I>bDfFO)C zMSFJBSql(6b<|un`U~X~mk|KKl&nnMHq-K^EngDMLy}sE%{qx@VH37BABHx7?r%(! zb8_3sGukRsX;rKLs`xnXz2{(S^9p_66oaCKj7t6OaoL;t4S3Ey~B^@G7va6pEaKN z>EV($5S)ihzqaGo-mBqOiTSP;5!CYHhOHkU3Y?H=X@m|~VGU^>8W4fkf*8SkK$Z!< zBy16^0Prn*vq@rLUxpX&^Dywo z6mGnha)8c9~+MTn5RW&5FA9 zIZ>|o%;umSD`Y?7_9QqI+nNYaaJ0E2`Y^ATPFDtBl$L(2^yzDti#^ZD~n4UXe zjn7p!xwAyaHiL4u_8WC_JW02T*y-<0=Jy&%|-ersm{j4Ik;12Ub+qd(Lj| zIhww=I(t0{$zXwXZ6=U99H-EQ>(>LR&XXr@9QolDbMUj|ho@8Q00^b}88GL#^*2cd zfJ7t}>XkA2B65~uJXn!<&~3bv7_-1B+<9!Op9cwlz)}C$mc)+ExtG4Jo#k&OInwFM zcC;McjVrLuuxIS&NHRts&qLF=huMC)m!C)F#z56PeS$h>)La|)uKjx+MOj_ynmMTf z1E~ckYmekAHR_W_DZht&$o zXen@=!_-+u>~rDb%)o9xIEez=psAx`tqRth<&^AW3~m#k2g(0_9>6Y|tL)FhH%7G? z;HXWOywNIL$&o_6TILSF^@r`HZw)|S zE3=0j`woU4zIRu^YKRAq^63RP|83El3y&#Ja?;=CSUvheSEwTDn^_IIiq~%?MjRv4-B&o?e5S3uJ@69)vE7_(d23CJQ_%o-@?=~OS8w~)s z)%bn^{Nc%F173MXW{+2R``Q?$Kr1RQGshvzR~2D-m*1`x zNw?GEM4OpWNX{4jGhh!xNA|dT;IYT+KAwU!({=%q)>hZUTTp~eUNzFGOk~~EiVJ}e z#1Mguy+vVMBa#5v)bX%~QDOVs7J!8D}M>1jZG>!Nm&$8`$wY|R~I?w0OAQ05}9FZ zAN~&u_y=wyeh+Zq-rfyDSyuu~;9(i@&yW`p_^KO-bkusiQ^F;CgKD%#13NbH#ni#P znM4|8ht;BWeKMDw97UvaQ5*~NmMT$RrpHGb!qsy&Cnd#1@wtRT^qdJ~3c@oNtT89X z@Nc(o`B<=3iP$Jib26@e>qd>`v?NZ@{t|*0l~{1dDo>z>Wd5Nm4|Q{MLgA(95i}(8 z&G+h4dhx0tuI)Fm@ErgvCTmAt#KKEOKP)fiUHKP~4=KO1FjCIb;I)BD$_64&_warb z0)zS0zqktEKbxf;-|-X`df%vU5)l2>l3D zui_@`>G#(LFzxSY4l4Db8DxZAnSw8z8HH9=-H^_yfjJFWLx$}_ayLR>@#xRKjp3c# zw!-Pi11T_*gu);J(k)_r(dg;5!(m-p7?8$c%O*IW8}gfAvSF>;xP0ot8g7YRLaL|| z(VmKCuU_&W#RSR1;Fs+PKQ2iOjP+v}j`#<)VEib@C1@dtk^&RuT4D%)oF}}f5Q*~2 zdPOm@>=Wflw@35*7~m^^ z-c!4TSlEx>nPE8$8o}8To#`WzsxjJ3SH$Fxw{d#iv9@7(QDhXQXNJF{HMa7;7?As! zEHuU%CMs~Cmman$=$tF90G*#~?F9;ce-+H*_I-nnGMx%LYtdDZnA8-bT+?ZC{7B#1 zUC+EYwQfGAwc7Viu*pBX`LjSuGB6I>E+y&F=0a9Z|7C|28THIo74OZNnnST40l3QP z0k8S^GMSga<-cu8go>U)e;4?7WuF^`)O=aK zf9sbwB9hoVoA-dIr@^0Pj6xd1iYfYU;5y+mKy>UO7s>rRIVsqQs?ELKm)1f+&n)^&)(^__gyfJo;&#NuE6U^?dP0P zTeP`G^jwS18Tb`;fH*gpkXw%I0N!=-fd@!-E(icc?oO$nTb9-4UBZN@+lHG0O%-w8 zWuQeq{>Ti8OQ95JkTp$!4s8@82P7lm)nkD~&7gqN(Pd(0cZb}NzZEi)U+GmPGqLww zeq_P%jf6qEHn)9lRZGLifa#AjDN*rHRI`8hH=eCRC`kg=?-fp}9~?Pmydyp+OH`M; zKuG?Wb(u+o+#l z`HcQub29_OMb)Ps>V}u>de8IxA0${L{CvLwa-A%YMv}F(2D<+(dZkcPW(NQX5as${ zAz{gxECUq^yr!krkRN@Y#*Eo~vigBXyH+!iRRtvW@XsVp6s+E@%HC%#OvDBMDgN-K zMx@k*r+diY-Feo(0IM8yvef)dRheoz2&C=T>#<}sl{ATqz=rvSdoa3svwjV-|C2`& z`Zi8-SQv2tvB;%XU|7R`wf$DuV5&-(ip$2ptaX1YfW3Kdw@|*+e3EQbzQhN6`@ce{ zX2B3KxCIj8LYuZKzhT=q+Y@ecu^UG`by+H{#T#iZ{2891DffpnTE5@&9$rOIdB2h0 z@(t4GX;8wt;cy8OS$7p_RP`45;>yZ`b_z&b6IJiaQ53hMy^0sJLa$hKY07rLdbDwH zsea>w2UwT%w8mJPlXrd)Pg?Jz#maeyB^$u`vsU1Z9S08g99a&|9$Y|ijHY}>9WfW1 zcp#aSi>0olLrD6jkFRvcj-t7~uYo@k)PYqqCiy zWBw1+)ZCjUxUGbLv<~*J99=y4=jJxxkX+))wX@~a8<pD5Jx42;hnCuX5MM zCyGFP91i>&1JyGUz2y6x@BK6jI`9%pG(DjX4#Y@cuMgQhx72^2K(1Tl^YLc7YH zXuMDij4H4!zx_OWow~GO2Un z7&h(Cg_jDEh75_Igq)=OtxOU5VOFx{@CXk%W%V;vaDJTqg7_7?Xwd%=XFj#KPZwdv z0jup~VceNtas3L^%ujnOU0;GO6uMU4>wnWiMKUK4%kCOcDsVx21P0+ZuJ}ZC%3wSO zyOHmgoE-G2Ll8+_T-(DS>g|QWjv6}Ybu=$j-%e=YC!0SG-kG(u+y*sb-S5+ZV5{NO zDgO%xc|>UYl79`M`KFn&ehPc)Nsu$gl(v(CytUVu!0y8u;!u3l@e{*Os^!vzrQwwx zmmA^5T-O4PNPhv;ZSwb0MD+R$f9+Z;>tvnkX~pb5L9v5Hk7yMc-2ldKf>m@~ToHHW zSYPxP&YcNDrDv>Amur$k{P`e2z{1d zNU(fRmQ>JXshN^^wcC5dzXQmL%!yooT@h~2TdMUzzjVv%#viyF(G);ob7Ujy#_eE8 z;Siwca;xU~OYAoSAHiSXbuB3a*>spW2ZtE5jBC@NVH|J=P~Mk_K@Z1{=){jW3DACD1fO1 zucgt-&I4BdhFeG*M;h&P^BZ73l<@45amEhvJ}+jI{v!E^y%eB!h-oTsY|26a;?2OS zX&ZccXy3Ftx4q`iSdP^Bf8gUI1#-Zuip5_eCFD$g#<3?zh0U;QIqIZ*<|#KAwfSvO zQXGoGSv6zfw3}&8>$Rgrv9SFb`?GF=DWg8Ot-HWWX8+X$>R~9qoI2SF$QLi8+l)x5 z(>kJB%K{gcP45f~M|*gUX3hW^#}41GmvJ!;?cMlRdHIptnp@O6Ht`{BhflIWg~~ST zOXl|jQEDvu>G--Kt$AoB_JYJ>?3y;m*#yO^@76|tM>e`r%69=2ZMUywZQ!|Ne%UP^ zGMT%ViKHgTwNGdQdpmf+T_qPeuH%zO@K-F0WNyQ8xF@48aGlu9FMUDt4Zc6w$JB0x z8M9G<@H^o8Z|t=hq#@b#OfZE392Rq(rH=byp3mUp%E|etxC!$;?GRwTWXFRy#e%fb zhtv1~tFq>i?4jFyQL~bzW$xWWPb8g&X5KG=Ay?ph@JfJY7UAXtN#`K<@hI}@aVk4yG{gt zHtX0;xt7`g6nv{LJ%__6Zyl3ytDs1U#B|z&p2<&9ODb@4O|n@TM*H#;^j_Hzg%;BQ z)1n}_fh<3}^k2@j8@{FH-n!qpTSrN7gj?=SGplPTt;!K>GmgFIGl$KbNw=b~5z$E4 z6mhe0_D@W3wBZ`OKNiSKU?XnsZ^iNQV=()Xen|PtCr}m;8fj>5;xBXSuxMci^UW1t zPF2;A5z1YdH!`|hrCV#V#H5ZwlCclm$?zJ`!ap9epfoyoK7BY{^f?2|HO)r2${)aI zHk%QKx5vdS@iakhHa)9Rz$>bD^m8*O<(p}3ur-EoUJ@4}hdMwS%4^;#pwAPktbJHo zg?d-XGwFCLFv!GB#mvGB0-AUig!%n@P5M(EzTj3g0UZL|TorfHL!NqlsiZaR$F>+e z$q2Yt7@IWrJL-tsul^=5ylCujfT;CLtxXsFM<2RWqkbz;M<1xH8hT<5?M|_9{}56r1S{BvjY}KNe*943uQU&T9qJM*On3Y><>c)C|+y8Z-DHVu;2n9 z8cP?1E}yG$(M0nKL&S^jQPqY2kIp1LUCS2`HWge$uCPP8A7$*9%VV(3^OXzY>zB0= zbrxNDixQMq`4yD(+_cEe&|-@U+rL>ul~iriYkr|-IEMVupgWtSwC>4igH(>yKhnZw zft7HLLBK;dM}N)`Ci>!a<6Lu0_-nZnDb%Lih79qBlaQ7Q+cXoT9_wK83{q^gGB)ZF z81HNAVA?+ABwIi8bRb+Z{jlwhKWE@h1(D82d0|wrG!=y(Xnly#lOhpGNRSlQu)m-z z%FBh}m<1n(ChM|#HiM;@n9iRXDv>fY@o6NoHk6_gt~qXe1Q`S$cb0{eC%YL4+OE1P ztJfRriX8stVDN9%0F9Fn5=wx8dXcNEv7q0{aRhvE+l_{W!mhehDTP9!Es#iw{|s;0=H0h^FelThpQxGI1384!1uoY&%AMH@9L#$Vun^Ndgq%u?`Y=K?7pZw$(v!LKk7 z^nwI6KgUSIQzUyYHc+?(%G>yy$UgDc*pP)tW}%BuJ)1OHet>Qe;XdB;6e{Ay=`g+c zO^-`xgkM>wsMq81sXy0N1%Bf<;D0~N40Mrh z>H9olT3{|~C+A#Y`>vDAwG&;_twF2Z_oZ--HtkerFa=67%b{If(d}E}ZTl4G#4P-6 zB9AKZav#E(y+5=Zy-oo<_|@n}rS<=yEzLC)e4iJE9u9gU>_Eh!(zkz{UCtm;{13Er zWkWAF5D{#gEHs7Z=3$CJk@`2$c+u02C63GwpYUFJ_hyBEg-BK7Kk;%{O_(>LD)}G4 z8(J`OapIe)o>UJw|~}V!eKChEO{UbBaPU=6Oe}s%-g$bZmBC95oVaXlF;2 zBGk?ALhg5bDp3{7>kwGZKo!lo`0$*|TDKlA7{ZL@Qp-PYFoFw64Hp~xgYsmAamkD$ zM)cWm#5zAJAMnyVvjPNazeLA}3nnuR`Hh$z_T=(tS5Y#xublmQVkV8?zc>cQs>I{@ z62{fY6PELmwe3jF4d}W`>P%L`0Xr(oh?UoFC6E|c&IRQ(v7{nME)Y-WNd{9g>|eif zSv;UuvaEOdb9dy&?&x670=tj_<(J|{8ybkkHndw~J`dhI-o=JVrqcFERQvGwYel3*T3EaVvlpsBfIfp8PL^ zSv%kxI;ng_VFi$KTJQarU3ZUdX71v;NhZK`QD;wwOz?MWji^0Jhd^z_YW};ZOdzw9HUx=TBA)6V4AMR)s=<0g)w4Y&2_X0)P87yVgQ(+XmrWFi zKf?#C+a!y2Zdz)eQ~^?%pe&wrnSLGAYZ@+h7bgj&$D>fwWe57@sS(p<*0FC=^48G+ zK|sF0r)!l}=WYqsq)tlvDW!^1nL*Yo$!JHw|Ebo2DG6(bJ<5%U7A0n)gcLGcdmQAT zAN5Z!q$tJ=&F;djbx6Y@vxFqpVBRuX9TCAY$0!XwW(ULJ!NQ@O&fs7%);>cQRHBf6 zV;{(%MEn$bBZ4Xa1A913!mh$_@X9It`EW!Gp_CG#p^7h{Ys5fU7)=KedA6!V8r-EU z##`34lVT(WmU#A`rHDQNab;@%3XyL z=ieRPCw0T|1RKs&_l({wGfuhvf^YerMG3_bu)==J(%1sVD|;54h3@)s;YN*#d9upLWS z)Ghc38{}*gKRG$Q?3szU&^VE;XWR%<`zAk_vT5G|F=@L_@K>=PcL0pAZWnRssB+r* zjJ~;kTZo1!$U-sTl?j-f`U&`?Gh6xOGa1&1ivO;F%QV1^dW z?%e5W0##sf#Wfj3+KYis1BJq{6?Qzj@VXNt@4NBvC-Hjr5Is5wD)+|~SeN-1j{Bm_ zaErU&zF;)Fd6G>|@99&L`s&4*RnKCVVCTtX>aAuSh;tfymv|7>&s|@F+SZUs$jfpQ zd6#(<2e0Y2(P<&lAN5BwdMN08r7|QMaFP^>NDxo=aRkUo$`{(SQ`rr>J zuE0Lii@~A-vTDI8b$+2UcwFWrsjGB_EdKFHc=wB>vM(*+3V^C8zj#o;nb06=a>9RkdI2VFx1Zs37|esv zrQvV_DGE+|6D68BR>?5=gm~U;-f_+WPJNtMGYWRMnjQ(H$$cIK+9F^nDhPFYiexX&gkqTjyg4>yo1ObrgElS%5|4_`zLvm(Z2n}a4clk4&0o5?He?M(i z35SwS6@w+DjCkh8yq?M=(|$ZtEX~siV=tm`tk!e&XDo~Zmdpw0AUk9DZ+bc6)3qJ= zN0W?gl5A;d0Vw=bf77mXbf<6Na4Awp%)0{c)r%;taK2< zTQ5p0p5o_p+(v2{UD-Cqn91wg(C8qFA{KZpI8`kRh)`r4{6S)B*rpaU;FbU2KqIYA zOc^+7%u>QTP$T${Kf5?5#w7u@Bo@`OAY!v%M_n|FQ%_j?!S{~iUkZitw5Skqq{p#Y_6`vt5cBS5%!mwT+L@RWsVUFRzOWYO5m1w4`)pjzV3(N33 zlvG~>2Xex8KObO(m#DL-N+IQz2jSjLZ}A?w4&pZvF*QvHuMOJpPbQF1TRO{C|5w>s zzwkB7rPC&_^qNOUKm_(1oVdGqKn1(-!rs{WzwxEF4MEECDS^QxeTGlo*zrIf;z~AD zb&pj4D@XRhOgZz);5tl3F~9YGI$eu--zrD#hg=}({O?Yi%4RVB4X-Ag^^N9uw%NE` z5b7#!E>JWPT!(m5mIs}7?WR5oGz?C`KJ#QpY`vub(Xao;h|LRPn|@^>jHrbZoU?;T z3`$;-ozFgvug*E?m^dL4|CWzss-ZZ!==r)#a>AcBuKZ3#i3A;mp(19yBVH%yVio4+ zE<=ej7#VP38}S6w{tIZKFG?c<>N60-`@lDs7V!NE>^ELv?K2UdX*hAM2!(taq z>Cggct#r~O8aWIX8~3Xt{S`nAr#0p#;Bpi%bWluhl1+w6RJP^8?p z7r`-(txrExt5fstZD2|-1aMSlI{lkR`tTSiGMn|jZ>G8Ig2ru12umwMuBf-qk;=s8 zd$vRinGbzsVkLmF_-&ZUa$>r5<&n2L(^dgIpyCRDn8oM-NfKzwxEvTVkXP2QYiiO? zlvtJ+;Krtwmu#7|lvzH9Ym41AcBPC$fyj%a4jva1 zKB9%VsAUiUoc0Cb7`2W;ef7uM(Qep=D%19}9G;&*B%6wherbhbD+{~dtq2T%u_DZ} zG~Y8fj_7-&D|f~k9#9<+{Vfc88O&{Y`0a`N_rf;?6rbT#n*nzTwRdioeV^OjVPG_- z4+LF!y;NnznMmMLr4CvDL|aKj!Xq)e-Lvygiiw0|F+|jLXZ8so1W04Yu{K=h6*lH+XoLJ= z_6D&4^l5oDRwNI?pTogtpuZY#SCv(M=05Z*tk!67{{zAi9KXz&3WU|iH%3sRZ zcWXv==vpnJE#Ml`2W^dIpOd)roTgBH5)?+hq2Ni-T8RnBMci3W0ZxCn>yV;?6K?|XsfsoM+?#`xfMd<)bu6v|5=TZm?bg$ z+N+!vCvG>fmG`$}n~T&*Wo!^oobbLAB?+Vn(~=;s6DA#mc5ESPctqhNzZ_2mi9zR+ zdq|H4`r7`l(--_Nq~3(;OLRt(Kl>AgX!Qkf?(0iDr5>d&h4mr`Cx31S zbh>4xf=mE&jv}1s#(;Ld9Raln_0A%Cu`2lte6&-uBlOvcXW=e@oPP?wqz3|u|7#Z+ ziApp)7E*_WU`fX$x+z3Eci?|jZfy+_Yn}ZK?UAju9nDsmQ+f=qyb4GzNt>cA&YA~~ z!wt@cW>pjFl@9WiSR!RKD{Az+NdG5aw=%U-2u89FTP!oBRi5-9KNm)Fy>ON?lW1bK zRMPlywf&cEnn+K@zpsVi2YKhk0BI3g)(cJ!jXGhDK`e=e+J=ri>((PKb6>jrMs2G< z#a5rXhw+kt_DCV4u^@PBPdxHR?CRT!qnzk_Y9Kky(e~Y;c;V@qPhku9JO0N;DWBmA z-kNV}S<6CEZJrS_7lri1z}7Euk2lR&j@^cl@sN;Oy7pz#ChWj`Gj_oLVe3ulEfN{i z9U#xio1Q+Xbvfte6xt2K`D-YBb2!A8L==Ur(wl9I0WzxF3$|%T$ZmanKg(3sEyFdF z4FH8yad|#8B+=0x=)D8C+&Dz&($+6;#2iQMLE-^z zS z*^hr^RTr$fD4#ri0SpB&RNrsF6W1RMa#7eT=cD0`O06y18@MfHaAcmNk!R_Yb=8kPBv*+BCmsuN zn}@ytxpZAdeUyp`ug5-_w(-e4Eys!tMi5>hWn@VyIt@=$PTVrXI3(25u0jop_NU;zWHP}Ku^EMs*-20W zjsR6?TR9Lk?*PL{md-RFtf*7ZU=twg9QZpyb=0RFWOUp@LCA;5#_GMzs&z_v&g4N&?F58V;Pn$v`Me54Z3#1x?3&B(VuTFe zWsi3dNv+8YSsD;~j2UnA@sy+*Mk>(HQ!CulGxX8VwHpvYS?2jd!=REc;rnM@fm04$ zw#3WFj8J6S3n?Tj=On!^SszCnu*uqY+gD0_0=y)?Fao)eUB_0lwa@n=!-kstuvg- zDdE|g2{5e9v3)f*O^Qkc1@pNjo`hXSzP!=X4gW$H&u5d6Vd)4j-REa8Ar*EWpJ!pz zoB(-&pOj1{-C?K9yB z931i@!St^9azp@$r5Th;yF|n*2RVg$R=Qc>=Vf-UNauE?h3xskWTiKII~W0 zJMKy^%pSt$U2m!bE=y@FG`db$*XF<@5+`6mM*=YsuE*tx4nsgODkoUH6LiEwaB@F@ z7H?^ML^h)5b(*}i`Bf)8d9j|rY|_fIlOvX-E2AE-ehGj1iQdcgBk^v{9px`LaN-yL zVC@)L_)pArueE@+Y_*mTwRrtxY{i%9(!}<*S4U$9>0lYV&JDXITT>Y)b(6;7dvI&o zH4>_z%?B;m5TqDHRyKR2Kjnjk2HG1hBCt$Wxp6E-`L13FR(E7#P$h+cH4K9{L+#Qt z_u7WW1$P!TNbVljGK@9^E#PQn(vaV$`P^*3Kf_ABRK3>=@jY8gCF*~QC}>O;YdK(h z$seX;IN~8$!{>p)*>BDzfkNp5)6Kujwvd|R?1%Ryf+4sNyzGM$jtO4iCU5yC__Wiw z;JL_08l3#&qsRxHj-7z!QVA6=9KNCOvxVJHd(c}Ba3`R6Ii8;H6z^>cwl`3t{aF(Y zj5ZU%>}z==io4E+wHQc2wT2bn;6l&m8Q2#Qv-Fqj31I-R)?U9?-m-l1^^MPt6n(%m(ZtH{s`(+xOuIl+$ z8OQ%PZY;F*yFaP6s`+ zNfctR@iTJ8f2H|4hlG}?kPJSpPG7d0Rx8t=6m&`S)hHm%HiH19?U57dm(a&jgtFMq$KsYncmOTy0?96L9 zRHpm*3HG4?s8pM9v<)ay?Y{Wre32%%n+%5oN6CnU-l0i}Sd z-OAkZ=1K7y`?lpfu>Yf2qR{MPGLMJ?lcaziBlgWbN9|okNkG7i}c~=H*4B4F} zTn#}}hY&Kt@*pR=$u;B7P zs#q3`S`NG9=~>zllu}0_6>JmZECC|OtP}+3OdYRqN3*1oY(Wd6q3F9^!&Utsp`!yP z1|vw-Zv=d*Bk1wNOxW?Ut2uMYBt(i38)CPIsuaC66^70=u3iR~ zFj}VyF9WZhmKpbaBYkm94($d@XTp)-VE>`Jr21ED9?rsQ$?K4fBe|aD8k)fput`&p zR#TC0owwpIkRp#jqDE&@w^lemJyi;=j8W%WO#{3g{iRDjHnc2Z5ii_JYB&ZiDrcCs z^oFOQ>W_+E>7HAN8klSq%}B8e=iL$8s{3Vm$0) z4Sc(u>ROo2BL*l04#aqoW^Y;@y*vnRL+T;)0;C0|?!{&%!6&@nJn{QFQxp~i*znDN z8_h|8njCSKEyla~F)?}ASYf`pz zwzXLHzTN>x(;L;WSkR{A5CyzUsW+oK)=^&PtfJzV$*!Pk+%;sS zVAT-f+AeTV^4^s5s3CmO-2oz*}U$0=?!ooAzOPv%sPBjH1RIH z*A{1jj_HWX7dbBlsj~j|AeYq;-NG#eft;EKnOcMj5*cZ7958%Nu7vB>;toX7qOE0< zm{@;aEC6U9zx~e$QpUuSdN#pd@UOx_$&=8{Y?L8nxfFjDB(@CBagnY4z~$fX3aKov zL4tfkv0KOKC?!>iTDsS0iQA$*7}&W5s!ziW(z`3|PDwzCU4_T!WhloF!roS)JO4sM zNS1XE(O1ncA7-5uJ$URU)7LCCkh0Ug9lXsI2!LO2+@L`5yUG6bf}L7LT{1Y_OI4GU z0={8180S;U=o&JfeEU%(?^CfbP!v*4&)mvxDOe_!q^%||VL_>2t}G^;apH5A+*z)! zWS4D=R*P3(&9kl|jxzWBIw-<<%pIGboiL9ve4V*Z*+Sgm%RW#kGYKDB7j}IXj(N1V zESk^Qf`&SZyJ%szbGei%^sXV*=s2z{)#&K`(BnBYlmUMGE@@B0Y7%{rei3Wj)S0CU zIQ6M`601$4e6SC1`P{{B)&v1rS9TbJUcL#C;Sg1BDMUmr5Rm|u+DPlHKZ&NC$hwq_ zfm~Ug;;ERCWt5SD9!^LSPN#T}YQfM=uTnzNqCoH%5I#+zZ50CUiO?3Xt8nZ4HUGXh zRjW{wHsG{f$?#W(Wx|-NTA9dxk5-mOIm6J3qZ_sop;RXnqGhvW3#BF9afRTCTSSI) zl6~=ZR-J+yz~-Atf8LO~eLln3A5v(Y_zL{cnT7#R@!Yt zuy`42V#o!Z+m`YHQ)cT2qKc;BVK$_ZyRd#unNDe2C87GqjD7RvI8>)BFZ3EpXvc=Ocf>jF9c8z^=O%OeT7?#o z)rDZVcnfyUGR88$=rT0yoySd1nwC?W6fl7J;^`-TL0_Zbnr?HGfs_MaC%joN;#KHq z#?AI)V@c#llrO^z8=k%bCWaZ7JW^$97D{Ht zv0GgL`9wT|wRO+-&PIsC1x<*1k9k3QX(oJ0#SN3;ro;Y^^`6y>x>%5RrHe&+T~wqe zt+T8}vOIYmPoFGNKU*QHg6gfmix#Ml|Q+ldp9 zeFLBlOXF0C&7-jL#MSVhPtF6KkMRI9lJeN3C?wKHL1WV&41pU(?P|BzWYgl9ThNu& zt3>FDWY+mdjUCYWJL}LnW+&o6pT{NbcP1yN0Fv^-3GCsQuRQS`j${iE?c3ugf&=vG zc4K~QQ>po6><&OeyI0@L((HJJX&|MJhL}g@fz{H$QMz2jHH3HOeV0$zbTK`{D=@Gq z(Hi{3%FC6jdXA8!~Qh&!O34B`v0&Z3ho%S zhf1t6SDbUH@i!rQ;B##+(1C!o4q|F)na+`R0Osu^Yqu`L`d|z~x1yoWN20o=qs__G zVCXM@tW5KU<3tOPiiM=p<>yLCs&KPIxvERuP3MQd zKU}}affiKyA%4((Gi0uN#0tyRMEoTYFICXbQzE|iBJG0IPcoz4H~bX2eI?(xvENn` z5;gGVL80#zeUl=@M-_#N*U9-!H`w5K_tpsP-WwEJ3veZR=N?s23i$MXJ3Jp$v@uD! zZ-R(__WVye6+bZCWVaHe#0suvPpa@=u_0Iu$|Lu>pxcJ!`5q&HVTJa z9mo2O7cHrRV^gA&g}fRSIy~MBn(*1^jx~pp2@x!cSy1;z6n_^`0imA{P5R_n+ZmF-PcLxTny7t+?ZhaMmpTNUxww5?( z4og#k+moMyNHl6dR?k%=Q;x>eMVVnf^sU|CVLn=`#6p~eP@m{TYsOQ7(^s2c8<9;x zwD`Ks?4r7DR_}{yxRAPpmnrG4CmZvt{%gYeg*5G~@qPMp69R_dwPA$yjwpl{#UyA4 zx}tWeE+T;q_tJOaMotC~1ojVTQ*NvBr=a*c06H!|ByA#Z-*=Sc ztnL#={i=ERk3%0LD+5sjkz-K9`NsX{Gvin~?Ih@ogozr#kZy3%6uoOc@aLKu$ z+3{WUJnESA0O0_RE$+nI^$V{CsKM}d0~dwz@SDY&)Mq&wO1wEC(^`HGyQLdHibyD! zpZ~+c2c{pnjGMPLZHR9Qq!!4Aigy(oFaAsh#8T~g8{=jJqNNG~Qi9mUrgw@cYV+zr zKe@SL>=Ah1LmV)1O`*++U%k>Y?*5qd&m!mU6?1P8U&3Q)GvnOVj-dc6I>(N8N6}o< zqq=M-HA2u9@1bT6Sd*f#tCZ9dO!oJUMwJRToMt~IQ34t%MtpY-?bs))MRIlai-K>= zkUyQ#GP3TzaO#CX=XJq9Jt;wXK%16M7_Li%tUqH{1K@5X5+e)0DJ*$QksejNpP zF5EBWW*j;^o~wJYg-7CjctQ)XC}|rkv6Y-kZ--e<0P$A`MlgqgqjS-8sYpJF4~x@R zdW{YqDsdxClVwxkXe?GQE)C}G$efOGe03aq;U0!oWaqK5M!fi86kTSTs{85i zHiJQF$LJ=g(7|a6t%aWAjmF_dQy=dFpj7C*+y0fgptc^cC{oO9GT91W<2lEdYT?bz z{%sn`uzRo8IW5N)u*6U{mYJtqh)NS7Y%QwXm*grQwWh1zjlx?S7VJ_8){YT|Q^-Pp zHEgVYgSH#HM0YlCrOY>{YAx(n!EA{X1ET%x*PvVb3Fbmyz+#4i+&%Yxl-#tz8K3;; z;!>RmZmP}~|8Bo4u)(&I!p~Rxgdc4ou5lHhH6OSND?`pb*R%%Pzox!wcvK-RVyGE9 z`Wtc0qla%oiP}}2D+ayIw;8DN-;gesumio-OkR~pqrtk@cZM(UovfxgD^mWrvVQ}B zQHM`GW}QB^EW0jeAV(^~xzR4Ve{tikJynJZ0mDp!qJCFh^IUaA+tIPs_ zmge3O`NfHxmQY66R3fXSesuE1kYJnPuEBKG*Dqf}BlPixIZ*<~=8_y;3H`E*a&Uh%Y_u*coV2G7=;V!LLpn)U_qAt%j5S=_o0>S3BaY_N;q z()Koe@25;_%+XNkwpe{-cSvni=CPJR+0d3Ms7TiPl*{s)`n(0Vm~X(*-8PlHu|RFc_O(OPgJ@$EBS+i+_~~>7hZ$ z9EV>YgI@BRrs8~oc*XLQc`RR8l$waw;r?;iZp{eZH(fKr1>;4hZBFSSdxu+|x}x8n zy5D&c!+0&P!q(q>@Oc_rMU46Pf=&xXQBo#qSP0n47LN|JJAg^Xp-MsqG2{1AbnZOc zut*-1j(ZIhe}6zz>`iTp)H`xrt2(0jCOAbkn{kyahCZm!L8gk100*96tJpJ&Y+S0M2YFmO)8eS#MvmCu}tmYCyquAuUN z@Pr!a0$BjZ8#Qu+{_o_Fng^3X#{)3sa-1y9kyi&2_lYbdi`h{2Pdr+BQi}KBm73z# zR?Gv1vtH?4Ln6jOImMZUs}$;L%aIhg^P<{@H{RrLvu|fAdH=#!M?+^cwjUtaru{{t z1?+}pfR6hI7RiAh>;YQLEFiiah(_MC%Sw({`g2ibQ8>(caaJO%?{Y>u_rnJ-?k=Q$ zmz7_0T!S#P4CRSX7j^hDL@DvIo3cpqu{!CPW%TP^Ru;);?c1$f@C_4rQCBgNi5O6#wTIUnIOOF!(8s6!jAYeDS2ZIerjMwp|Hnc#e7;{|=RQ8rG>x z85ALdYa)ts87G3v4SP)7z1@Bj?94za1ZG7qR!{UtY|8I(MVUa!Ziz?Taj z{&_Q!?8MG;Ph*+J#(-V7sU8c;{~Z82`}{Ky*$Cf~$}7tZPgWMxB!%V0tP0kpxp!rW zDFoPK4jG4U8I`|Rp&`J^nIK-K0MMK;}j{+=<@*vDExd>qYJcdrsx<+0`>y?19x^V1P2;UT|-kW zD#;u-S8S9+#&vzLxO`PZCdDXDf9GP54K-etLr_~DC0pPDrldm{p>V{#2^I!YdluIH zB8B2!Hq zpmNS?pao||$snG)YrhM0cJ|u1~zDrEXWCp);Fbo|JFka59~+ ziOFMu`U~TwKlhkf5_3>Sh`2F~Pv*bG)c|UEae(lc|E!g+=Zedv@3J-$K~hRp&AjpX z9@m>Oq+I%73oIGqGLp|j=%l&bw7P)6M%^BIfltD$M=?{-qoY2_Fb@|mT%XB6pNCf>}C;j zF8NBDekaNr;_?^KZGpJ>f9BNpk~ z6MMW@T@9P@wIQi@p}Ir-`BO4m%xC)o2fJbhkI#B*fWf>|c%srW>~MAxg& z8}oYuGVbGf))NI=y`}e9KhSVa@1yV7h2Q;ETDSUQAe_+C(~p8oOd|Js+C-BZgIQg? zj$h+YQs3Mso7;a2H6{)OSIpBqiOQw!!%XR^hN`b`9~}PS!mr5ad!0X%&T>@pOcG-wIm6je9W6?<67)M1B_4_@h~HZ-xy^qTTG2( za1>qK$6ltnAjq5Z+tOl-lnAgSYeFNqcfFHc-uQJ(jEX{OJB8s7c5wg(>tGO`Ep*0T ztrD+E?8;96@cYW=^{q>-uSOp!Iw{NT47KmcRNlOWg-=1E$QI&c~IPXyWNy= zR;#U@IpZ$Z_YSMR&c!kwvk+6vtrs5Y@|m4ufbr!&3QL<}fqJ4Rpr8A~1;k~)P3zx$ z%*uK}9YQItv>}tvP<|gpa3Ox3xwPv-r(3olLW&dN6lpsB!=vc%634+v*V-0!HrV10 zSDYh^)4hiui_4rV)erw=q`3K8kFMVgRux94ufLeQwY@erw@fHj!T~JpuMHFm@7h>_ zVrUawkkhndjERv_^bq&z96~G`DA%7!9UW@UZ zvYVNcme~wArUJGSU8#83jU0Z`A92>`ZILafUCf3fF!^~*-*1GW(fT3^N}MZ1*5i%u zdfmQz=YS}Thq0BWEmUNzF$A@|PsPM1(|Z*OpBKB7j;B20#klCbP`aIKA;2&6*7T)@+qoq}z87*ClHi1r(1xcHR`->UzYYxL!g6ixFqnxaQ zrB;V?KWpfV)5VmA#z=|krpjBzj!*AbO^!9|tdb7MLZ@}i<1eI=I?6j3l=-bw``V-8 zdB};6CX*+S%}9;(Xz=FN82?7oy1^ zJRfpBJ{7dXT{lx*^;($f5)e1q3c^AIP|8#|k?rT>33s`S*ELLIv)3l_Pkr$|$o+Es)M~$>VzIgavHSeTD4PV8(53|GkgN z_o0T|WON=U(tH>Ka~1+&w z#6>uN@_a08npTk2xNTjBPwnE(MRW$gCcEX~i{z3xx1x(B8u9@II5H5B()Mh4(@~;8 z6tB0xE;4+eF#@2}+eKs|w&dxFj-5GG@Wy<8p}@SP0?SaVOtY}bn&8kyFJK)#p6eTsn0sW0 zAas!YuMcU+n+)dIYW|`HE6_ylgEc@1pHm%R@GS6${YX0l?YW zRxeGXlA#35gK8|iz4*4@#dvDGH><$06f%~ppo{}wa=4LaDB|PctEm?Hr#=PH{$3_0 zheyIX*r^L{Rw;NUGyCqP?3Nhm&f76*y z@5p&lWxH!1nm=*k|6|4fWup@adJJBmT|H|#YrOnNEIW}#ziX=D(}8j391Tq9M5vmv zQ`1TAd3mgMNF#}kRp#F<9`}4y$iwfo5$cI!xRm(8Z7NaWpgnhGHYoQ4qAGLC6*1~m z)OoqwUUuoi&s&oG&0X6}GHbTJGQ+1XN9f9i|Be9B3D=nro)7i{WzVZ_`>vUTFVas6 z+N~y_zRB8@Veb|LlYVLb-V%2#Jp}9sFJHvV_)-RB=phakARzCgB1(08)m?sTxbdry{jXpV168;n|Ja z6M}#+-p85TaMa9wAJDdTU&e3e8JD$sXkMU~x({qnVAciJE9*jd6((7P?AL z9;XulrBy`+v&nbPEtBs%%YyZ{RYX6kZx3=pd&A(DxSKLy@78LX#ij+KZpJ8Qo4tH{ z7QK0OH5gCF9QG96lWKjbU?bP7cFA7Tw4Y{d5GsO2XzWN3>Z-aY{C?+%G_tS^&J0S7 zffwX!Kg^p+aKmPiB*;%MNk*5LOAQn4X{+m0x47AtA^;iS5QQeI~iUPSxz z8ntLJDt1Y<-GB1uGN5$^nmOOuK6s8*j*$!ZX9OH#BwDK8KCXDD$JxplK>jBjIT;;s z!V1Q|!kd7pd`I_3)N{R;HFtcI6S+P3y_q$YI99r;ke&cDi+NMhO`%!b;WT#CucxWs z9i|yd!+t?Y=Z4_hga@ZP)K9S^XudM8SC~+;1?4>U(NSt=NwKu!BRcw;HYQg$e6Rg` z`@$VbG%aj>U~eRmgu&}|X})a49XYFT<+wrAoL@JcAyk_=XAGX6M_x^NE(G7;4ZQ*8 zFd@d)xjKZI2g&Eg$l6ARsg^?H*9c|OXZj0HK);oT8XbtnGRrrM-4`F_q;UFQUZ8?m zlXUe(0PL;R$<^y225Q`@I}a9>HX5-qR*!GL`vq@gbw3C4;AET!&fF7Eqs!N|iea74tSOj)Nc901+0!rj+3v&UVvYx{#37(Pt`dqi z35DJwQ3yMEhadOt_129(QkZL7=rCHzB1XrQ_=Avi%#&$XGO??zz<|Lh(g3C0S|QJ(~#NNAcqK5D4Cx|eHvE3Ki4%hQShBO+=3{fmfwEb zF)f~=O~X`{F(G_zUJ7(AP)`!I`r?`Y=zOf45eA!cPb{-{DJluDA5Z1bt>SBgzRMoP zl<=}RKK(xOyg6B$ie|rXoV8gADw`VoUnq(&^=$@Xg5K`6;SM?HT$iKZHHgBM*z!*V zsWF`B&IO6RU(jCfH|gtaZg%AQPuUCEsDgjOo6?xn6vd!j4`&9cFocAzj35xR;vtYI zTMIttccV#`Gcs)HrC<1Iz&0t{m)KqWT*F1POSfd z@~Q|81403|27QKzkNM9_qHl~w0>0Z(vKc&+@mCQI1yv6fBx-O9#T>r(mFhALOIqH* zbQeFfMw$7{Z|}ozW^ZUpsd{Rqge&>g#|~8}mIqgvw4Qjw&U?c;dgokKXn8DT(|_;;80(rez9T@xw2`&hBd=g(mr*t? zoCiBA%(5@;*PrI|X?}MmBg8+dYn>y&5&(+~wfD6c$Q)NvtbBw&YeR*Y2GdrNMEUmZ zjWgW!p~U3^MA@0OiRZdDCFV3DHuR;WyVGwir@X!G&af)jKKkG@XA>pt9Rv9C(~1hF z!`Z5Aw3~=Qmj1OK6$*+NiUi8Il}y_%@uRZ&DmG+QE3%RtYlIg5Vz;v%KbRU&9}>Aj zcGd$1BiAxkP(2agKmyAylu71oO zW4WgHK1hHPv!Ld^Cxg!vDpnxd=B)lqTHx?6oU?ZCp?d}K0|qi{0U;s$j*qpzjnu)H zo*T$?Z_l@*p{38AmuqlRH0e0_RfrA!?h>)%LVa?Ewk=ZOnpTL}@i^n{em{+JG(`Pf z+$R4ACJBYks4(sEMm|*2ehxM${c}@;dd7@1tPGghERSChzm_1*wc0G(x_tgRQ@r>Ut4R)&FEN(zrmW zy+8=PUDoBKyc;VQoOsh!kYhrDIT>2(wPgWWiMNDa2M^{5!QJ;$Sv%(RUbMCZ@z1Ur zadUZZ@ZfvkQGqE%3KrfPg?qKDvZAOiXR&2#1{YwOaf4>p##FnP6wRXTBpcl`2_d#x zLP_thw(ax&-4*i%{u2MKX^|oK^`a9=nqv9e;_p7cJm!yjWc_QH6=fzPjZi2;+=aq61^EW(f!bD2Kmg|8Y4ArUE|;7@x^w% z`wNQ7ZuXL_K0ZKTX_XdnrXf#6sUWjmcn{?s*btBb0|^MFO(VJB4A&&pw;#CQm|wei z;kXJvbBp_LG~fHL&Urd%9w&f`oy54I1JDD7pdU>KM7T6u(5bV4yETz&e!cjLrgt+w zI|Y!8FLBwG(!aO?wj_8+sxi4Z3@ZDsRqa(T7xRPw=mY=o&HaIldx%q{A8djHox_Pn z9qo5|s!1ZSLU#9+Ni!fLABylrk3&a28)u9m z%ke%|;<4~b#LNfd%xU%r=1Ur>f;jn3CqUNixfbS5)6Y3{f}j>cUANIXOi@TV+dVwWT4i4mX%#iEp3BDKg z)QmfR+r`JJ>Lx4+%^Qr>;!)>7QM4ycE;n2wYxO)ru0MptE{+ zp{zeY*0YFV#&wDq5%53%!pbWHR$idvOK%@z1WXL^6np(utkxf+%sI>c`IU)mWBReG7>1tRkkDe#s(BhhGf480<+|9@Wu z-Onz?a_G5=n;>!yBA|#+m&+mgt+ZhXnLmW1HoT#SO=4+&7gDNU<%D%M)Q%_ceit4u ziiIgt+q_2uxY{1I!eBQ$dk#OhgUw4OLj9LY+q@CX`#5ZMd65r0BslObXM>*2m1?j- z?LB%f50V0KAB_{yw*l;s^D!I68Q_vfy@}{#l-RbwOV7B8X#?$K`PMU~ef}4{plpWQ z1y?sST0QvtfcZjzLg2TFMy%qyte1_(v)gY_NmGQ_T+}NU?yG_KMaC=@FD^-`$7&%c zn<3vd0}Ox+fe~Wdh4(J zb$l>kv185Q>KTcLyj0sL1oV%zX2GT5OJ{k>n(2Fs+v5t3R&Oixvc4&S2r8t6Ut^wp zQuLOhSh~K#!{l-{`CW>;bZJz})`J(|lxB%AmrJ?PyUB|2=1kY_C<8O&*<{3NpsA}u zHeCNsA|CB=Vbif@WAjFgC_k5bd?E_;ACS0fg5CNWpF$B3E34^h4tih`lru_k(7i$3 z>g?j!x>FYAI9XbYz+Wt>0S4eJ;Qf2{3|{y>wi&D=U=r7)%^55fNCt0E zQ1zLu!TWfllcpU=^s=jV!+Wn(c5WWUeH7n>!-{@_3SCWh*>?-kn=0iqmM~>=A$0rf zeOg4_T4m&)S-EfzQM75MT^_!pTGrbE8&BC z(JsDM87&T_;L^#Eu)$&CkOs68`dUFS{gyp3kX`Qy2fs!M&fg_vWB!VD;Gl{%+x&Ma zRc2JCI8PM5JR{-;^HEgkC^7c0w+}Gp0^W;d19P6oYya9r+$A5^yU z$Y4_Nz_k50mLUQud^;TE7f%_dp@|Nnc~&Rr?nsw&3O}VE)zu+EZ^u1yh$5E*`7HPp z{sg@a305cmolkhUy1Mi;LUuC9uy3mvZ8DQ!3H_*W;Go(RS=hWXIvHs4XUa1Vp2V1v z)Z0*`>(nXTojB5T7NuA2lfQ;aD{W}|!RpiGuio9Mt2u*|>E>k&NB3YbLa2LBgld?5 z$N9YQ@t_aoOm$Ra{0{ZMi_#UKf*%bf9+GtQc?xj?gvqDl98cB}D=Qn%D#hf0tGQt= z=AH(V!j$!r24)D&r4a1WI;|`z?kdK<%l2)t1|?)}Cfx-ES?+yP;>zZ);P)M%Q3gGf zu5<$q3>K7XamK4B1ZLP3?{b0^=dnWEImCmZwb5Ajf7G0g7X7>h%5#qirolJ3fjge& zFuxsUk*DPwOCnSyQQ$y~^%y&xfyaEZf@S?53vKy9zwqkKEA=EP&TX#g-WNl1v&34+ z``2Krma-xxMZEL3es5#N)+$Y;jS5&0cO3p38jc@a^Sr%=klK||DrO%oZIsT$OpptE%OuMk zgYSaJf^M#&zJKIs-@n3TbqXe&G3O^l<5?c^?P-|%B-f|v9Ym=<+Ba%koM=D#<9Igg z7no-vT&p58bS6-dLbdHeDiRK)Lo%ZWSK%~L^ z`=cgx=f&q_6`$EI--&NbEYHbVIX!v6erxFHOec4~M4m>wQx%11rVWr2eiB*RTdUmGtcXBdvVka!Sz6@VG{A*y*WBUsqwHjhMn`de^^*$rx zbJ%1+wLGC-mP1D3gt_3hW}m>(Ev7P41xX_Hu*GVbs6uR_LZ&ol6Ep-DOat@$eE&(7 zs%)Q*=I~%k_J=pFeT#g)IJRlSM&}F$J$P<8ez5Y~MC`UJy{Jp{N2WaU@kf@Nu^SH@_&gy@EJ^LRf<;*_i512&891VLTx_G3%Y zC-&Oc8<)q|?j-q$eM~bDxxIAP^q*+v7#sYp>naGW4?e0X+ySszKPXS#LGIkg$U84h z38&)jNB%NDu$FdI@ZdoQEl0^*VURB+O;zdKsdlb4@H2fFLc7RYj-2u*vKCvuzcyfO z@{!)&^(f+%aS?sknqy~Jk?FbJG)Q0W`5rv_)d#P6zZVsHkS6U*ve~;qvAck=m7VbIh}%mL)uG+CS+$-0Pye(T%6w;C=7ljCp?W2MR@s_0q`2y5i+z#4&Z+CyX#V?7yX*{I85AU= z0l?9aH(r`GZBf{81N`yh$({?iupuSemo=W7rrEs78dNU2Xvjp#Pkfx=U5$(B+#7h)XS=>vTPND zN`8hZ4{Iv@C>uOwV9MQUtAbjMC5oJEiq|n7hJK)=Ic@mUTEAnu8+%qIRP*WuqLSF+ zT-`1px7fFtCY-ucB!6lC(m_NWp4u&AE%lJ1%sIhXwnaLPKxqX~HvF-tEWp47rG;=C zE6$A3z`Y=m%upY`{wD8CTtP7NqLU*rXPD_nI;{PsB>!wyhLe47SXi8`B9HAEg%e{% z%S<0is&6bz4WE)bP2XSJ;AkNFWtrse+r0qvmCM9}zt|6luPc?$a~k#*d`HwM(V_X6 zddpU|O+XD{fZKZ0U}kpK4Ft&i84e7U?hR0|IQ3E4EqAj+T8jL&lix-QU1Ji* zW~l}f*CP?G{c&XA(WGp!4|>vfk?TKXR_hy+?*2L0Se&S~XrG`n-0l%?_mdb;h}6Eq z#RSYSkM9H)PzIyVfxTg26P>XiPcsr_?JB66`kcL!!---wzK{Ga4*is$@1u{`MO>dV zCJxxA+`Sj+?CLjfwjxOE9CASB58s2ezIJmiTVUj!^D~czaz|q8NuWz;lsvl)UT4sD z@$a8{x&=Bk(~jrVdTcZ-@oQ$mx(ADLs&ctn|D;ruYD3)UWip(d%x{q=dmMfXp-qm(Vf8Lm&ZJG zjpV>io0LfkD-}+3+)I94j#9-O&*?VlaUtn<`J^5-rGhIG_uq$ewHvN&LPa6o`Fw+o zo)&z_J(`;38|JTI016TXc*mW^!MY=rqu)k7WC_f=@p9WelT7r1M*xd<@cxb6%YQEj z=K_B&B9*A=TlXnk4OgFXw-puxzaQ_|+%Q&Jj`RhQQEOR%4Z@kj0eTf&WL%eoNs zBThpNo$IHS)%6Y#UhG=*Q1!SjhlH$3AkRT%=Vw0#AlK|qtMSk-)Aku>55&tTlyuL6 zju5EFF z>uHliYT!KKI4wsWVQ^0P5?#IU!Z*f_P+qm-O2Asn{!?65V`Kj~vIaa9y*hFRA^pZq z*A~FXAEI#cY?Z!wj0>-=tBK5-c5;%TbIcsMhrP_cQD{eab=|sC4Q+hCtnk4e33x2U z*}RBGgL!M`RV{@{c-oPE8=UttPe*g+y|s|iWq1_njRQi^XujFQJ&8@?OFsQbk|q~1 zP?84Tbg8;v>&{SLj?eIYgP*cv@kUX5h2>0E?B(^ThKMQQ1T<+8H)ey&tQME9eH@kA zWZ)bN6i_w+DuUHys9x*yvFcPb=25m3#!@2zjnuFP6WakaFZ> z=*WNKMy+qC^~Vez;d&abqL6hhW}8 zf5){s_%P(*#i#T>i;HW`xr>YvnaKcypO9EG@--maYOvsm>C)VP?INNJUE4okS>XoF z(RxT`v3-|^b{TQFVUZxJU~jN(Mm`VWE`^EjM{o=8XN1`rW}y62{7GSWo`^VQ-N;IK z!d&sv3H##9@54cEqH&FkZVW>5r@2)qK~ z$+xm3bjrAoKoQibkr+2joY|!PFP9!fh!pZoz|?x4Wx`+uZ4G z6YLHB732y^!Wc4_62d&cLs{Ip2HD)oLm{W%tA&Nr_uB1VgPQkVMudu(Ew?7AN++4? z8tN@MzuY=5axnikiwCTZXU(|!(*#ug`s=)J#SZhx%~p~BYutFsT0N3CkCj3B2j$ad zXrsHzg`JPtr!ice8lS9Kd!G}lfw>Em6=9t8hsnLjj2Ivz9I&i+2qn;_wTcoV0kCqpCrcWhxi`Ntk%CV$t>*wEz@tr>Fp9OkeqeDQ{bm0A?7BxzvDGO-(9aZMsLJU)R z&KeWrRhDT!Hk`^vYX~dEaLf2t{0KDqt9`GviU=`YbI3)i3=G^1rMk7z5TXMj<)tow zkcn7uzdk_j96n8Po>Xi{5S~z`Km}KUYH=>Ahfh)4GC< zUg1DkvFAYKWhrA|;O=%X%MC+^IN5BK3GK@da;J%vXm8AJVrr0B@N;$z)4=lIM%_&G zY9jE)*)qGl=w^UT4Fty)q)TI{%Aai4-k}8H>zLw`45fSYmFUE5%T9J ziO_X4V;s89ZoqSGAlyUmlk5>o$H#8?;~q2N5G8=QDhTFRwOiQ2B$AdGVe0q}nKa~V zew+>+K(WJQRV;8_nGFktf0aGITfuI{OsbS&2p{qf?6sz2HG3DTaQQ&M;M>z=IGr~M zjJwO27Zw5Z)yPJ-INpPO8ewLMa#OwCI9SBXYcsAmk80uc@DitfMhp$3A(!#6@g~fq^yW=Ju7r7<;x#1!+R5neXQLF6l;}fiN@3 zgSV*~4$AX9wa>jcWZ+Z(5_sFCP=|+$wTz?`6kJgP>Z$cMzne^ZKKDNrL)(%2zt*7O zDD_(aVpK#Fv4B{pIbhxphO%`>QYgDTlI|ygJ`sPzE_VF&r$YFQv0;D-FkK`?S_A{0 zwM?+Ht&>9_h$ZA3PxqQWmXpb)4LMp2Izk{)*!u_)^<2&$uK#GP+ zi5HA-Bcy%+;$&7G<#?Ocy9_k`WzWriY4`mB9Cx(Qf*_LKbLl+x*&Cy3QF@mR@ljcFanv+aau0E~lb`2^=7bo*V{q&WHK^!Hk4f+y;{p=$0akW*T{PCDlBP5|3tZLI*9!JYGskXZFBNqaE(d(W8@g z;L`F9P+En0D>&k)pNfk4tTA4mIJ0Qu7SI)kA zAXo}oOkdYTp(<0<2qSLi5MI$$bK-$U78{Lhv(FHTNfZeu8ApwjH){Rmv%?N8#fpO* zlbz92w{&`pVltc#gGli`gGvh*F_54vDb=M`vT+I(=DMV$ouhhwZzd%T6NLh{wg(Yr zv+_~QP1|qnTas1-qU!egCW3PghQ0h9Ze8yx8A6{?=RUMN2W&bK2A&j{?1hXRbPfPd zB&mc7DNx!OdyocY4;QTgc*f--{}Ns}-YM&DRM+HjN3W=XR=M{#F%%MrAx`cRJe7gW z#R8M=jKX+K={|jhbs1p3Rm~@)+R@)n!8z&u6}NLHiYeY`gmYwj%d4H8QbCOT(JvL& zaO0Zv`Vxn3E9`C0$-UK~KqEy2-+V;~Jd43Ve|bPGBQPFS$jyh4kDoKd_qw_i~#I#H?<1Uwyb(!6i`I;?Kb_Ol1w#6puoEm zYrz+)%P|e4FGaLy{bR`;jrF6LKjM2Cbm=NI%0fu)`-L7@O!w&DN1ob30e#hj9jzT< zen5J1S6ccR;#@sY(Mzx?c^0aziRjC)A6s{qUG+2qI&sW2h-#l{m zBM=3!yM`u>q{!jwZF50vs|&*z4R7*|2SOMh_j)bA@|Oa7&%=Y9+wPc{Zrm6I=dU1%xDRdCIT-gP_XD3F zYj;pu4`QBHBKrGUrML#XkYqoS3Hh0Z*`Ki8n_i=e7pE5LFz<>u+gt}vYtuj>?fei8 zCFMayBL!=1^zr#LQP4c6JZdR`FuhXkU9l1@Vb`DCbb)&X83dpam)rJhOt%EbMS;3{ z7azO@F5VsrYR(d{D=;1(X8^J*64N~qmwz&-&hu@E>T&D`h#K|y7EeVEZ(!aU+P98a zjex*Myts1A@#N$=gFj7R?4;aGqWq;RfxM zPDIKtp)1BuM!sP$X{C^}MeDwv@@FKqN1^U5HYXmp&mhm@mPuwYLV!8~9=dEeVQzdDTjsopEwP7?Qc)9>wJ zU$h_`n3`nK&JOsWvz0m|7j4AoOt0|-b$ zUgPqh^XdUCn2KAUgL3UInh|A>LA(;=Gp^@IA_;hwxMG7!(sw)8FouBOf{p%D!}{Dhv3t zr3K{>=G}1+{>Y8$%4-ZB#b#=a<1Cl~qdlio0?c{`8IKlYn{rEz>`Yia?eb==-yU+p(NvG3XU7lOQ+U^c8r^;BQcZn{jjJH2su;LM4^e3}qLRoR z!j5NmKFfPgLXzu2t#qQW?aK=)ih={&QZdQTBQaRvq5E4HUga?Ka-G1ZyK!!-*sdG^ zhxPgdT`H}Po|Nt^oTT0Qso(VHWu)7e=%5mX!cet|O)u(B#`y##F)OcNuYB8oLpI>j zVe84>Sfl>fzo(j$l;Y>$ebU#(1&`}gk6ihUh$h_)lf1O!Jz*D2^AZgth}j#gJn$XG zrNBH5DLwW~lnWSB{fNcmkmr0a+7#%O@rlAHz>AJWNvrys;$m+^f8<*w?RNz86I)A{ zI@1+WYcfgP`l>z(zgMSHcm0ASPO&R4&~tu-)es=jP46t=yEa}W{Nv;<(%eTLVKG>M zI<$g4M}Z}`;OMx_)R8ZfS_qmV_Z>;{?6c0s3}Df)uj&=i*@De0jd~)epVZK9G@;qa zwjZ4NabJzE;8DGuobZ5tdPO47XnB`TPb{dk)4j)G)>8;}@14Sp2w2TQS^Yv!MN>S) zAf>QQT=vQgOSl8@Zy|QM-R*E-naJzvE96Eh`t2b1;qHtC8;gN%8u_i1?3GH`RNAN8Nfd4WgfRjT( zyF8<2X;ch2yf1$IGHCXuAdP2{=Vd_2y}mo- zz+E#PNpfA*M{Soi6q%g5?V6RQAQ)@mR}D4hnc+$Z~?)DM!hgG!O4k zX%~(Fan+SyeTmEu$RkS6_AuwOw*JS|oV&6J`)3V(-@iBnTbApa)!SJc#V=3M%%BiD zB-Dsb&)aj}JGozDwOjoiQhsSw=7~v|4r%SDEUWB_v3X8nPdSVFSc*ti7RlK~CQp=j zsv(0if7wUFsOVStU-s6sco4)Wtd4+iE;hPd_2GOwdM0F>eynQ=HqQ}?#{R%eW*JfP zeONlZE9?IW^%khR!MSOlt zG*b}6cQ#Gq&ySE8R@~SChe{5zpq_1XwTK-~CN58xS-pd)#OWu#nvf(_03eLc+fD7b zGsKJkwGgqSuN&tlDqCVvel0U^iP$1^k(E%M7DXuC@`wM-)QZ#~Zd;mDY3a)1%+Ng^ zx*)4Cq;4ZQ<1^P1p4r48qb$+G)SpQZ>56=4m|hEusxU9$3XTn8v<@PIynL*E2PuMa zgh&+(g*1Ke>hGx?yZx>$D$Z+m4HaU=QX3KN3@PeO&pdvl=%QwjCw=;}=Pi*pOlp$H z+=i+$rB9WJ(NOyxG$TC&F2xPPbCeIvj}yJ7JqA{1+PqkZQiG34^f8VP5>WnquIo|p zYvZak?RCmEnscDgVREsv8*;C!fz=eq$@0UDVFW+v^E|FV)@Zci#n^;bQVzdZouwx-e6E@uSUDGdRb5D5M&@B;q)X({!Kw?e7Y+!@6$Gp7%b+zEryAD}?+~;7?q)Fa^w%d$!f= ziO|PqtlS4niMs6|^ZER#d+Y6SL?!ejdc;pnQjh8Ubu?wHvMc@Rx^>>tI2POWYoT|J z!4+uCIM{^yBa%98OrKq&81olu$r;hO7$yre?>p33!X_;b(CLuyKZ~z6wn$;;Xr2a{nmbXj~Jn0L~x{Wzs`~IKBrjL5)3AQwg@E0(M~5*|fde?SwQ4 za&QjuqUCWY>jAL&>jSA+uMZrPYt9v=CdarhknL1HK3%q&d#VCo)} z*qh}!+O1558cCn=QO03~bU}Ca(%*zQWCo$O-r&syhYgdVIa|O$aACN~zW9+bgBaE0 zjgX)|1SN?SUXz`LpI2QX#=B-Mm0@>l1K3`~D77-}310(-&Wn7R?z?%Q@;ru#y97O> zcuWw*=mk@$(oYFMDx7?{SC|E=gt-pcgqFU(@| z9p*_|6fW$@|2Z|S9S{PZ^3hr~?M0cyg$T{tC0}?-bHb65WR5B7No0z=*!L$e&7#Un zWD?!|9h{WNq)1UbF{jY@gfN!NieC$BGk6~ORt%shjH9K#aF4`um2q5srDSHT5sQFrCxao>RV_|Dz-SiA0JL01hWF`U*i?Vrh7Sw zG1{R5AQi4@JHuxrd(kj;YtMZ=fBISbG3@)ziJ-Z|bhZ_&J@}jMaz8QIZ1_bEwCb*z z!%RuGWSuOcQB<)!d{kT3z|(AXg+`6XOsQV5Iqf(EPybXB)^$PpnR%>Ce@NNRtJ|qO zo*cg#VZ8{rroS;DZJ7Bo9hm=k17CICO~MtroPi+AZngTD0{(LiqFS31D;)hvN46cy zCnNML{MBMOQIB1sZr=g&(LzhN2GWL(D^t;m1^Yv0qLN_`X*Hn1sRH4^Js$^&47x)S zQ1BT<&M-B6Y;}t{iq77ywXrOa>oaOqS*JeneTzmRH?{9;WvP^opQp&%6}1Q~${H8c zIot_`ERM%KGr&CjhCTrWvUVHvGMgS=WEm0F7gdeQ3!g(~VW*WRxp}e|7O;GzzVb%< zNxY_%W-$E^-1NVPZl&9jCRp%hby%kgtn4EeX98ebet{br&4h?g&Ejs&_HEqp(QKn; z=156H*=Pg3w@NIo3;}hm`?&p{21hr@B31jPP-LBlqS1TD=>Vh%d0l1&nsar=nBWe()}6v~Be>3c!c$BKBr2$?e9M&77Z2axLHF{Nq5u1>yNM;{@w z(6|y*Eu(gBm`>8Yd(+vqd&UghsQSu!{NI?RIKB>7D%>kle4-+y(vH4(Tf&K4YjoA zl2ymk&uFtN&OjW)(`5X~u;U=|>Gk21Q6bH38C#Y$M%=dMQA{#1>sSaN%m54RD^;({ z=RkY%j@|D(BcXLkml={w6ddbLTbxi5j!0ya6%gB?qBRn$QO$RD-?K7OwnrsUT@OaW zodY&ClZM@%f^}jYy;Ve(1DYtvIJ*a_+#j@1eK;#*uROhX{&FIN78Dy!NS*`Y&g0n$ z%v(+I+=aD`BLr^PPl~}v;eG-^^bBihGo#w;AFm8nG+ zs$d~>gBw!zZ2~wZ|H|O-jsPW8F$3)``j3ps`YwDryQ`t-E29YDsymA3RTHS&3o22B z@Oev22kh!_$b$ftO%toiJj2NLKqhdIBLwJ-hW~vi26G=9DAKSuHVZaUFnS?2cefD0 zwNIR|F{4s7o%N%tt89omx0`|$C|M4%XLVX}(UtNCASjr1V@3FDvm546sDMDWA3Q!J z4<$EufYqYU`XlyUpYmSVa@GXdPcxgK5Z0{q9q7u0Jxd}VxF_SbU-p;|nJILD)4e+y zUDD1hTL2jDzKYn3!@H(1sT_(Gg$6w6eZ(NNQz3TW(0B!FnbQKk~P;j*U)mivniIDXPAZEzZ(|h0eSPsQ*%(*3W5? zdKW(yAdTVju*p6^siBqXc+5(5?`SJe-Z1ymvku~1nLlgtR#{_o$wX*YMIo$UZF4YN zRy8SW*8ayr{fjP19`)9*5TTq%<#i|{4*OU8DJ}F;7$5kJ%!&&1L-egrM1l;xn!52v z<+n?u#rE*~J<-BK2cdBfaG#WzVw(4Jk&=!=msC*fN2Krya|8~@;yy>|R0g zhE>&JYa#Z!XjdCjFkgLt&$_-p%*W{H4b}1Ln-}v!$6J~hAmmy&BF&F|Y)W^V{`%Hn z^SUXA5@1-N%OB8JTP1SzHJ@gS#OK=uroCGZyOhX2&b+|~JJ@ZAo~JVrhxbc1{NnrS zOhU-OL=RdqWuaSYcb!}Nby9{Ft^g1{78Sr3LA)QJ_-w@`k`s(&8QZIx^t-7^ZE=V1 zC}(=f!xYLYb3a+%>ZGK%CW5@hXnWwO3@js%dkG6JuN=YfpU7pWyAjg`hX*88JjLVe z1c}Ci#s~0aw1|?N$6^PgfRXeL=uvC33BW$tl^}9V4D^k#ra6Hsi+%ku4AQmFx0}jq z9r2geOmV_GUa`b&fGp4!s6*PclrgQC7K*6+UtuM6f<6KXHvF>+R(d_n+@wYB*4-dJ zc{}x}TczWaUw72X=I}0w_*#)|is5q#?|(FBG2-!TU#7mbdR#nRbhswV>J!D&ZniLO zzz-9LR6?sP6bZa4p0Nj}IA~JYA2m`73jvqgL^LLgMce)whY0XApA7hDeP!&HI&ucY^3FWj>hQ>?VNLCE2CFv;$DuV>~1Ku@V{$1}^!Y~k2bu0R%=UL=WQ>wOY$2_D)5C_}-G ztvq*AqnJhGB?qx8ifDc~(isCrL+syAHX{tC_l(X{x5L2K#4Ht{7BLjVEDshp(d( zIf)Io*z*JXl91NTHa6!ELDJdiF40_VcpP0XgBjYhtEKzharp?4x5nN^j=EJn=dUNn z2l+!Yw8{>%Z%o_ILHcuyq#|OpQ^4dNE8}#QiNpFt_0DQZUGcxH6rhf6Prna-j91)9 z@e+qyu!l>a@b zK8@5*q$!CU_KY;HBx~w7W%_N=nTVft-Nx<#IoV^W*DmC}SUf{qsfGq$96`y^i}Q35I| z3)uD`UcVn8iku+Fv~)=@12WCQxpzfg4+Khr>?yH;WuGnwi^!U`OOVb11b;^$m{D=o0>(48U5Pu-FsEdzVIvcSSelu2n(&Nf*~;tuzk% z@HUhW7^eKHVsZ{JCPZn+3i|eqaZEBVqyMN{4zaVez5s)_&-}2>)sv4>>on{2TgZx8 zM+T*;U6VAhkQ#|P&r5t*#-rG!K`N)B+8J0ptb&|CAGbHsquv6P_D{fPJ$v_(+pTXf z7}}5zLfLoJM#T6|A9z1~MToELm6j|16DL zlnv%HMXchuFY2R8qtGve5uS1B%ao(Kx^09}4;)$?C6zlesxvOo!*{@?{iFVUsfb(HYNY#=Hb(ba=N#_cquG z>DMMsZX-$^n0p{+xVes3x(C62A2Wt2Iv5)2^=e2(r3(_(w?J{S|3)~=8y3+cThRd3 zy!m)aE!-TdxSXS(BKXNA31CL2|0v7nNo5`~mECX=vM4S1l^#I6%XFf;;s%{ZeNbBC z^%?T*`Y=NJx`%0kv7Ow2vnUprGK#DIsmDQD)6}jU+cc2qY+lx`zXm>T>RY*wK73P# zX_7bTG9(Jm1-FAj*5i-$+25?TtKN|j0m|XWt`LL-6=|j=Cc6)V#o%;cLle2G!Dmuu z!@)QN^l37Lw>nElSjf2iqkJ^27FlcF6diV%1Wy+pulo)XOqJKBv9hJvM*7=5=mT~v z0>QhLcYlL{A3VQ>yMe9Q;~>OP0pgt42X{fo|KR6+qUy`qn$2L{+`INeuUinOFFW&m z&}0SnHI95tb{df}K9nYfFVjEc8hLQR_6M)Q4=wH_t^ zafF}aC|%iyxG7xKUTX72Cb1d=0`UXyvoj*MaMW6!!mjVdCmyG!GHP|_Z;V-qso41% z8q%=1z4(bm1LZMuu)K);6Pquj`t#lzc5}IVlyC1ZyTilqm_z5BYB4uBvSP}Ao`gQA z>?|HU$oMXx(Lsoo0lYB!LbIGnX@)GO%?kB)m7VtU0u-gc$ z_J8e4T-0>229w}X1y6HbFu@eDeh^_AaP|8j(I~uRUkdGI!U!MWd**a1KU+^9?bD-- zEH-A?>F@w}x(8>$w3r76be_`_zM58ac({+yr01Q!Zr)>vCgHbzwo3G^z5Q$NO6+kT z_ErJDjZ=8HYM@0UB@l7p(qxVbVWg54wqI8oqfR~6?QFIQhFLv1$srn{_r4U`fp1%w z7@X(}up4ck>)7z6l28E-Pf7$<3Pkz{>=ptc+OQ3G5%WY#Phb6VQ0sQ~VK8&!4p~%D-YuNLmp>y5-~~2ONOS>-I~rgkGkX;>>>I1f;8M z_KFu`5g-3GEDNLpRSj^gs;s+5N)QuZ2@%_Qn=4^%1v<+#jVf&6$CWpCB_I!GET)t=2GuS z|E)?&cBLjeu~6APmXUjRT@7biQ64JK&gU%_@bAbaj4-{pj~k*}Cn zYSbr^f|~g)6K~{#c~Qe9O;Ysn{+{WhOM_#NydA7yt+8X)#_~I8(dAgidGGQl!?1#@ z{sglu(!kL|)Y`j}x(dfhmCL-__OXB4d!u^ne{2n}aa+Oz<79}f1Qq7sBe)R3mO1)E zZfEYf5|z)P#4|SI@Le9=bzPmMg6JlXwC1Q>9*q}n^}*%tKt#2=2yV2;;r(dC@U*?| zkkk%ZdW4bHNYKsMLr?Z&3#>!=@PxjqBUJ7M7_~4AL7npBr&T|1?}T4lP6YESuL4W^25^y-^|}cMcyw zGx`RU>+7dWCa!Nu3AU4Vd`91H7C5j~oLVW69T?vUKlz%aTEG@oaqh}Pom@&!gD_1& zSn1S<`bGTk3_|5E*sL9?>I*8>FrnT)I3woh&F)cNK@)O!hb~Z1%!^a&5bd=SzHOfR zJ=_l_pq-df*?;PC)+@_Kf^iSof9;LO)4%SFq zQbd1u7g%Gtf^_#DF6!T?WbUh6liIMXxfvJz) zH;315oUDLI68w271qf|DM0gXzQ^e#I4Sqv{h`La**>S8G`0Y4fwoFHbh@(jqA?#k@ zVm^FE^zO)G6-9dE%6eE{Co8mAghgb&iwl+d?aaokDRaPag99mU(&-iLGVHQB&^<|3 zYk@JHP4-Nd9@JpD#M751^R!m`&Ab1e#8ru$YoLpiY0Mf=fx5W@-Db@{WJsW-1n!oc z(aAUHkM8k}&g$NC{5IF|p^bS|^-MjeAy*1=7DlPNWWY;FtM2WVv&jkW`GHgi5cn08 zl@#4l?T^ReXQ7jJxrWPIcvPG+dUMlr0jdWf5?87B9Tmy%ue@l}>Eco%zhYfN6>3lK zuKw%jXxHeyCkOVwYF3Ha=A%S;p4b14*Q;FJP{RU{X zvRrHD351d{!oj9%8Kf6u>5gz|F@_)&&50%$Im3~od`>{D|MwM|B>k=}IH7RIu#{eI z)-Y*Z(8ba8NeQVpd+0u1cUcEdG^X_Et$Ea++J|O*mL^#NW=#!yz<1v-ogJ&QcR!ZW zY}6aLgP4?Vr@LNByJ$J^*7PU6w83Z$Vj>~;=nwDt$?7#&bA1Z}WnaO)vWGgbUmt6DZEOygg3fEh2=|-x!kiugMl{mgRXPk zk&o2Jsf6p>@ZP8VxR8zflX1;M9yIUOlesT@3kh$ywjUqzTbU+AX*zvSAnC#>luJMW z8$X@VxjzdmKu)6#HjT{^VGy>t5Phge0H_w8q(h|Y61*?WYJxm!l*8ugSP#RkaPsf6 zg1fi^Tyb_>MOvp#`@7@HpiFWgNSWuTQG&4w8<}SOnLS=I$P*aoTJtvxfo)Jy>baae z$ooqXHFe4Iuhv8&0E!WsnJ)Ke3Ahic$qVcwijHz#s7{7;Dm-bh5m@Uhcth`FEQWVM z=k0!p1)c{QX;aLG#-22#d&eF|%Q?Vw%&e;2wHJ4^%A*xo1-LF($}aHXunX&e~5v@wR;-a!qrEtCE+=8ehkUn z#2}{&pfnsGtGhUKrw9opmb`l-zWw#G+uUMOfly;wyZ#z14nBw5<0ISEjEgD^2m8=P zJk^ziH!=AWZ|aeL#KV&H0QOq+JM2BY5Lhr27XJ~DZ-tKxx>3D;?L?As@&)f@D732_ zBQBU&!(V(RPQ8zuWQf$+qZ7vrbm*&_I)I4~r^$yc8Nv7dw+HM0k|hDLWQUUheN)V$ zaIM-#m|zRm;$E-wm2TGHbk$}V+~TB7nMI>DZaSthdDW_fS*9>G)5;1loh6{gD-C7T z#+U;uuR^NABrrC(YDjvzkD5C>C3pW^(RQQcScgnSk|{G!vfaY%?#^d_jRWEfnV~#X?Ov5gVhJ*o(kfbzuLS0L%7IqA6g=(|p{bWPv z#~225`ajE6EaT)hjvm~*Bk~sax<>OjvH_XS>tTu58-g6`inK;n0Mn8bYuX=qMVCEUEsvR z*ftDF9T#@Iq1JS`<#I}}=4WgAOZLO&QF@}+zS6zD7Xt1pGXHZR3@U{Au2U8*WrbW1 zmhK^*)psZMAVT zAZN_>VQQBA~h)c1N%b(fDEU?_G<0oUW{y zb#_sLB|^tU?N?{6vIA#G3C*%8IadU zmK%w^3apyd@8+1=Ct*n?Q%Ad-BKP||z0U9p_7igUs6!dBRCHjV<`1BO!6k9Y+@#R} zdlbG(shn^V%W~y1xLOw6)wL3qGbc>-GIX0xFHkHGH(s3Z=j^^8Q_X)_4FaPs%7cD$ zPZ7S@JEaf2nSqvZpUC!?Me@7U8mg0S7h>mxSxt^#ySuSxmDWR1uijW{AjN<(0$a~x z@KDW9m8^Xmqq@P^{=UcuBe@C=t&IR$+2q_7urtI5zHpL#XF}2-a~@bm@0QCwtn(B^ zR;$~Vc*!Ph-K(S_6+|Va#e}OZtP{2eahDpHLF|rEVyI5imHJ0<;1g(h=kT`RE>lo< zaXp+?U$TYd;==isND)LElK${?}r`i_T+R5Nsr?oaqT<4w$Kr;vhj7me+ z!Jp_nvnlfhN?ekPUp7cT83vD0czV3O-3f}kx|-{>*T1`{hV!CY=l@7B1=Y<=&j}G; ztH~J_NetY3-ZBCfo#v$H16R2O&VnA9;L-xosZvSpm0Z&J!`V;j~cy}-Z1Q^5ls zWXzyx`pa+>>M9?$ShGOKLEeWSlC2TtjfCo2-_8N<;?p*LDCAv*!ZuH;)EW;_aMB#s ztUf1sHpk_9mIywrC^ZC%IS`vxaUB zpg6RxG}rpvAqkf*Ix;h@%6M5LBsEp%lR#_nNS{)Gi{mpmJHEJ3Rzprh5-ZKC#SFHe znwn5U4&v+qz)bK{N?>eKYGTo*PMRihJZ&3VHQdpu zi}pUJ)3Dpd_cJ~rbRwg0w;j`66iGd}KzD@)&ILNEozJ*s+S1w7zn9+Obpb zq$vPx@tyw(#)5-Dt-tq!k%aVW=eDnBI;c-522qn!<4RJ^OT}=Tvhkne>S!f8H&DppG&r_XZR96B&+BDpzX;Pgm=4=Op5oBHcP3dh2k* zUmC&LGugH39MnX^$2l`{Xv8ixJ9tmkrmkpQSNL1>H}AQaTw-hl^+1FMT$ePWczP4E zL9?!M1l<2!?YcI$)J`?fixcVk-tv;F1X(t2$wb-#%Uw^tQspBnB3;LjJ@Z9_)O^~( z$Y+b0_KnAunJ-2zx2{)#SK6It9++Hu1MZcW?WD7ADX@HFMl{ z0(skqSglB)LnzO7N0Irs#TxJk7p3<5;|S#@{)VEnyFd)cxrRBzyHFu|q?|5@LfQ!l z0zzVLf{M^dJv?Kd!s9lPIQ2Q4?O{Rg9S%j9>wLKqxP^WaojiMAAg&iHn9;u#_?p`K z$wda*J(fwi&xPHPTlb?Qr3hF$Pg>AbxFIIcor=x)i2{qeJ*a&J2xQ$vYzR|~a` zeivKj11HJsVW^2^j=EajrxD^H48~S$rt=N)RqjEh3Zee$t!J-+$#rNX5C?cDNhi3c z9TdIp^R3smV`fOJ*r<>-HqUxw98%#wtN@T-4)j7C z34;GETJ4uCxc3vm9))fo@ANug$Z6xdJmrks<+4*I>1W!Pergct&z4$vwALAb=t-q( z{Jl#lKHgEeTon*BB#OEtNz;D4?Z<_rvjABih)P~!(KwDRK8;NrE4yc)xRAO z5bj~Y0#=l~HlfpgW}y=HZv9F00%z6>PSo#pf%{1h=AE(Z@Xx@g6#cv~Z{Z#0@%GZ&R^Y&k3)Gd}51!!7U7eQaT?7Xepi}Fq?@-Kxtc{$|j2R zRMjJP4LlO-f*zg?&lX(Dpa-?a{y%)=tfP3BZK`$g^tO4@n3E-{@zP`9-|OD`1*t?h0r+KjOIipprBOTF)Z*qXy*|i4iJ!ko^qS5LlC+)8ACft z19V8%jEevwc(x)vrwXPQ>I0=;$^Rs%?pkg=TjDL=2um*Ia8gg8>QlqcP?icj1j#c} zPmV$?)^-1MSf=?xD#}U9E1LW}P|1_R_B*6I*l(Ou379>^`cPT@qP-axRwoI;>sG|5 z-eekwkm0+>@(JL>&>?yJLyV`_2X4Yfnm`eCGat({m)(H7Yg746QsvLi_lIZQw} zc3CH{zaG1P4B1Kah0K^4s@iR-)`fl%Qd5P+04iDDGgJu_f+l72J&c4rlr*qH-1clb z^?VgZ=n>G4xU(1=!b*}s93@G)5^Gz*3IShDifTYW1PS`aVarCynk2AkdT2A=5SvRA z;i)PFdJ*v#8_Fl)nmWo-ZR>}tvdokQaNV_2D7-B^7zxMlO7lW|S9F2o0R-hc|7G`@xK}5M|)X@UU-6+qQ*!l{EwdaBz->?S}5t2 zDd`DcLu^F_n+_6qV6a7an6wX3Rhw(Mw=mmPySs3kV0G=rWU@&C*I6aaRyPPX3tutc z3>11a(?_nSB2*Ycr^&VW-0V=%inz_x^kwyVFTpZU;QjJo{vJ-Dbx9QGWM2p|SS$*@ z=Wn?h?wThco$d`eQfl!RWrQek?N2a19`{cp8DmcL?V5fk{WPgiqFC2!*%E++vr`Z~q2inh>!9==tRC%g(&P?sne(wG8P@Gq3F} zTk&vn7-Tz&3jHG6;_(#;317$`5;_E{tRl1*{{XW=wnY@ySlrUzdw?xCY*8g`)2q}A zBZ-`52rb^QaB=EncdKa6a-w5^|inBC1m0LR{mQhYzaRnOIjB~xqS5t z)>x2Os^XLYLmq|+9wRU4d*syepddRA7aQwXvP?465S2EEbl;k2Tx?}@4^rZX+%eT8 zuGy^m1dT#(qF+2s900Rhr%XiOK~5K5Lqn)OJuiOIkuQ8_>PGRf4HVvSr$iiAbRRNs z+Lj0YznRY(+y)yMD)tK%1=7y`fYB3ln;MtcCRZ^k-)*uD+8_qrbP|23rTI9se`F?Y zn@GT-Q_H3nQ&v-y%ld*0M!=BmNpfev?#?|m*b!O%tlx83jtbQbx2)~lMO!yqnA7~2 z)w@N`Jsl)Ez!lIm6!6fao05(5n-VcEo9_BX3E`FEsQJQ}fJRqX> zgMI0?Vdnj@9aU$MR5t%Hxdi-@ty<{_b&$aMgx9*(C$NGtqr~#(lrUuVq=Cq1_qqTy zI2&A&7E)gC-}z;#YuDz(;q9^n>uA996yFR_Tg!`uu8j!K)hq&ZE~98KRqUd}rd+h9 zVo%Jhm%FWT^!`Ix`ERUN{IpElz>3P6Lw7Kip~fi( zVD#PCS~pNsKIt!nsxire^Hf9Tc{&BKKFiLhybdA&v~azT3xfj`M^VDxWh;94T7W!D zkk0Wv6wTAN`f|x#(ORXJXM7%C<-#VbU+oN@0Sje-q5X=se`rc2VkdE@(oIahB!pzb zSDpQYG^nlez!Pfv4gE@bz4+-b@aEFpcpp?suPQYd*6U~*uG9j!HTxa&S+?^VC?1w4%tzF3kvPjoBwmC{@T?L3=M%p8V>6+isz#7g@|p(wVMB z4`>Jo{3l22dDiy^PTJC)l;hWn;wEetzW zcs5f`Qu>POms?eX1A@(IA1iQL?MnXHV6bUiV`tt`*eeDoDu7X@bG?OAuBTDAQo2Z9 zRn3xZz3avwj(-0B`3T}>c^JNPAn~;c1CatUudhdnN#gN}>DkUJ<9~N+cw8xfV2|Xc z3<_wV8RXnAXKn8|$Rej%f7@zqII!^IQzL7=3AmJdCj2Bu<_fmwgiYmX!=xc@X_W}D z2t`dHVLgfH+X=Um;ZI;8_o*7#r_aTf$JfN5OEjg%o3{-|fv`E)bZf%XsMSuzx8h5S zvU_JdU*`1}{KxlH*&wK z$yj(jf_mEa`dZ0;80o5zZi8cubBVn=&zB=KZq{T5lg_i1nJm$(uW|u-=}PqHsGi$uit70uZ#MTQiz?_X+McY11xGFy5I$tFtl&qigk!k~Y|3AN&O1`yFH@WTg zY-Wo7i%0JnGiVBPF4nfq%pVllv9oi8>>)~Kp{d#3i>i2*w(BS#kvtF#waoZ)i17gt z4bT-Zx?Njcp4!*+%YeeZf4=Kh)1dtdF-FgS+~WvzR<{x&tOLk2WS`|7^P zG!T`(4AY?m&M2$Dz zI(-AN-)txT)vv&!;SxWWWfD`0i$|7YYwxZ28(8Xwrm9@Kn2{D6YoHqF>=0W#wwbB% zAq7FzRsLELPhQ^8q5zPvOk$k6Izo>FO-Y~00P<%tSggHTT*!#JCRqOf;$@;6gzL6V zxGnw7Mg!BDoDWy@dDI{s;_%~jx?Teob0Qr0!OPq^xb34}`{Y1fT@JAFuf&>?v}W+y zwwGXXep{n{!hmYpG3wR95Pe2GRWIK|%+eL*T>NjF$1eWKlk%IAFFZmS*5j?Y%Q8yn zP4sh{XpZi<%59~)+$qiFI-G{!HV=GM^l96f{b97TAcbQkgB$)%%kuNAP-l0Pd9iMK z_ecPja>?yp`l<@J9fvi#A=R;wm+10yF+qLFxiT64;$n0Z-`oP@>FIPr{U&-zW4y(2 zsI_R}n$gSM1FC{|0>+7t)}^=kULMDJAN5woKpHS?Ovik_*yzmK3puVdS0}i%NckU) ze~~fK&zXuZFMVXspHor7bNWZyUg$sb1lS-$4{Z|TFk2AqOMZ?q{W?tiLeAtApjB{< zq^%|%7jT>1&6e4OA2RV9sqyX!wAhzFRPDX{ZxokJLU(B6(E+ynqPC^pb56U7pROjg zNrFxT`>z!xIj3TwCM@vy?gD1Dws5S?Pxo2ke!a5NOnu(-p&@qKY0$Rl& zV`(oN4D!Xjw`Az{_uP<%b9+Q&c7!;C{~g6mK~17+Z4$KHG}6{)H3u35Gw8i~)d5W4v^2xTLTgmOVeh*N)b`d=x$L=k<(*36;VF&p&z6E3e%p1o2S6 z1}TEV#RZ@_&e4>>n}^kKyyMzFAgffjfrjf z`NJ8)4x+i|pO5%}p&57B!qbe-w8@S=X`Si%|Jh)0eVr0?=t33aadJme6| z=PJ_GbYuhmbm36G47-*;SEdW4jNaA~aj`Z#cOgp9jt;FgpwJpdc+Vw=jG>?ncJ${3 zJ7X|-+*{|C*4dO&JjT4NgAKHNxfz2&pdMC$L z=bV*0Zw>9F9lLyRp*L)ED13Vjy*YW^!0IcK%$`ws0D{zXK~N%v_cc!;kP0EWrFvpn zkxYCAUYv3TwzwxbPD3gDEDzB@GKD4FHgOgLeC>1&F+^_0lpb*=5<-tSa9X_kgsmM^ z3or8yoUwn*Y~P$HG&G3*BVCgmvG3+oS({-lM0x>~q!)iw*GQ$a99Vl#w(FoOs!LL5 z`}=!*6#zS}e2!x| z;RtirXT-F5H@%^YC%lRj$e*x)MNa0+mWfW@i*`vDW_Zs!RrG0)&+*9h;p{Wg>cti% zHY2eD14MYgHLP%2-&G#)3evRp4zKmG11I&Ei3xXU(HyrcbV-VFXm zvAP1k(%M`9mC=Kk|8Xu;1S=%#|SgAdUk!fB$sl<#7Q@J8p_fDNm@TG zIIK823iyykHUGA=+3){U2@&_>f2Lw`e_m)|kv=_S*VO@xgpGpdgN+qSl%g9iJ0W2L zp0$>=(hM?~&u^C(gfjHI(p&f2ddR6%WO`45L)o~U8YZar&64h8RQhfftlmQ9Z%AusfJfc0`slPsJ%E;6Ob~^L0!ORC| z?pKG!elIRj#;KDZpA0!?yP~yJiIbUrNva~M-zW49)_X_=6vHAN4~#rB4B7tVf1K0;lz@2ymw=L zZ6f}sR6DdhqG>h)ST-wPey(?n9F}iFyvnqU?Uw3&=psXH#b>vQ(d^7&72AIl^gf_) z5Z2E?*B3tFWL@z@vbm`uNkWk}VW!#Smj&S#X@;u`{rf5SA|%&4);^HC#ZL7MlE4w= zKxZdpoc{(0s541Rdyu_O_Yg8uTC*~}_ZhHcJ1~B(6N-|juOdE^7QGG<9x$NEhX7C7 zB4pD@0P0O754yTTe{sN>5!bc`Tbq>D2;gh_vhzK}tPj9K{X$0_IvXY(X1D5q4lx%@ zV&un~`?{u>1yI&)mpppCV+c>h3vs7QfXWPajYTI92gwUi`Rw<;h3(sq6Kd30@ZFUi z{NlBp0Ux)%xac7Gl^6H(dWh7pm6%mHs${TSB^DJM!3FcbQ9l;SLq`N*WTy^f$~cSd zkND*DvPKs<+rt_AN5mBt5;DxJ|1fS4A_L!_+aQmTPTAMIs^6sNduqA}pP58zg;>`QLvWf9PtQP!-C<@6}sBCYeUA%>G}UzngN(OlKt zV1epAb3M}rwYQ730vnpsH& z5E5L- zJHG`eB$hr2%uz(lufjIn55WMz#XlQxEbEwYCgRTs36Bg~NZQK7M2(%kK}}PhsT?o# zDVepLCtUv1v^uI6e*dp_)Rlq;1R?rVP1%v?IQwvk;3a~O?+PMw2Yp)xdCDtlfGJ5b zX~7u2Gcll-F<4zHf4~x>5YHLu8=CK$Nve`CVeH5F#kcqKd*6Yj-mdqXS-+;`9 z-0HY$06t8n3`s2by<}thQ7M1E>hA}O6ZA<$X^6OZT z5}n&kwCKVA+p@390v0twPffzO?M-v1Bg-M9j3k!pndEmTI1#l)8aN0^S=Kgkh4tph zT&UJde+(vshR1908B185BJEBt(gR-nxNGmoT)nV}TJ-r>n&?{bZ=Qk%9a|OThuKB= zf|KZ0Yn9R&CHq6s&OD;(QiC|K#K>PQ%K%PMsgg#tFc|PFwWJGGP{?#nwjco|=ZH&uMSkHE(3FePUox{%wK%sFtWp>vT35G8sqlg&>uWaO=29AtMUqK+!x`A z=NWdXzf*a`fq*$Z2ole2{P&RC$yeL9XC^g_ zV3LMtws9l(V!@DAt~B0|+!4}i%AY<$BF#Kc2gg{%w2PAg@uhQ1-|K{=-m`}Jivx77XAOiRV-sgxM& z17d)7mCdgLYDSNR8hPZIfdtC(NT4i?fka-c9?8)hDG|URHQF?u4{L>$NNj-e=zMN% zU*jNj)$aP`?L^l|6ev698a6yP@o!|pPYg4Cg{w0|+3)TP z|A5ntJ+Zi>)&0m<=$J0DP}h+tMJ+KaTB{d$0CQ|}>a z;#cER`1|UmwiYSgJ}R(~e$;}ANIR9QvOJWb${jVZ#405(A)vH6yKB8dh+ZpwdoW0H zbr}c}r|^=F7*C!Vran(lt;?7(YB`+}Z^pV+EP_FBv&(OZzdKe938PF@n9X7y(eDNk zd)}wyl3DeZPlP6%SE(=&mv2KmOqfNnW&+x|{&xE4)#`GIJ%Yp6+KrIz>LfXiXY-4X zSB91(^!nYofT_%_=h5X(MaPnP+B-FC`)s`4eJP~V_xz!#>76)n5T=6-0C7rrMo$uS zTBS$ZfUO+7>eiK>XLEvYjuw2ksl=wsN^^kt@s)5xe@^$^p^pasK>~0r@!8!9fH$4& z73oEMnDb3j7vN$}zuok}%Ah#aVSnT?w@3P_;uw#e7QBAP9+OxKwv2^1=mG4~b`(1Q zT{J13Fb#zLuynrFmELEhzO~gB^xlqMyAY4BFcbuPYV>txa&PMq|1oqq?ag~3SRedbu?_yKa5>5HI;(e0^nto7|0{Q{piIsuP&&zpYcWZr(s@uiLQh0)5H2W+GJ zy;GE*9^qPm)(;Pb$4vFVz=xCgxnuc|R%!Z^oU7Fa3%;fEz`d-+E3q6*UfR@kP)F1v&DUNhY{xW^ z`ofabH=@a2T-%ZLLz++Q{2K$0BsPqM&Bb&H0Q2@;GQL3zyy2 zK$j-?V!1N*GGRr(ANBsHiS6Eoo~z2yb5*SR>$F)GFy zYMHOJ;lX%I?gvpac^l}E{|vWi9idLd7lrUO6n0zzfx9#r2Js?WY~ilmwEdzZCq|aQ z4cV;%glpU6Wt$w6hE6>3R-q`?CMylgm$B~K6qgicZr_&||M64~KWlj8)u#`gz znORx~r)I{bqErYCR75A~i87jBuuFtohK(Fol_w)9Yqr01_&>IFC66fL$ps3aHQnt| zy`A|bniFQ#vNp8$Xn%LAOo(jLWuh9Q6Sv$EX%TIr$Ui*t!Op}Z=YbOrErV(FTW?;K zKU5HJr_{!6PWv1h6yr&^ld?X}+&#gt@}~e5*}+wezhh(y3@|rIjvX!0c<_;unc4ujl72ScF1Rg)pMN zMV%f=AB7n^Dz}+rDLY#m#6%9=oa7(97wCslL&f+=xnh*Vn5kB070GoRJX1t zyCGpJFj1;%QH^~a@!%1g>K)Q!IHU2L1Dz3ztuvlL{mq3i*dp(R<_W1J(t~n zz&dOn2n$~z%2vspsWN9)mQ8Fj9u0=tYL8C}!$%;FHN_F9sqD4hSJCQ4WAvDm?ye>1 z01R1QDms*?rv8p?sz!%7@`*=lQp>cTL{z_xdv@C?nU!h|7xFl~t&-Ev z6>z=GJR?%4@N|0C<76Mb(u--KtzsLc{0;pukE^HOzr@$Ipm2W#Ok{z1oPQ45;z4a!NhOEm7z93?&k!{H{D0L zTy!4mcN~SmSyzON;J>#xR`A0TPQeoch9wz$1m1plkL<^CV(gruq*-oO%hcF3PuEWA zGv!Z$KH83YOD)d<@&!0>^f@@d%X1jsC`ORhD$u?S&~>D&?TAxWr}Ea2n&*U@w9Ybi zF-`P}6bKv^V$x5)y>SjpZW#yIx4YQD7>M#HfLBvUR=?bN^T}V(8LBDrmiZL02F5}^ zTw&A2CxaZudp(F62?hMfkeHwc!NBd*0-R~}*ydCeMf0&eB()ElUTK16VxjwH$0BaL zJ8#dQB!YExN;{Hqy2{of?k1O?0b48!(lyCcZ1d1zXlWA$yeOdWpnRQ>+$Fv#px0r9 zB9!fUuk|X5u0~R8*q?GL&eZ!Q4|e2ulcq3(TpHcIJXHOiKv}`!o`;v8#`|};b(@RG z@RRxM7+!}c2yf)D2sEVv#xm-^FWz57E09%H8Ddj&*o=wd7~fTqX6s_Q~nnM`%4 z^Y1#qI>3z&lPAmUAgmG6FvLW|G_bh4%KhZw>bA$$ONgy=KA=@^%MGyV<^^s;6L>bh z&*CHIH9V=oM5asl++ddxaIC9m3W)c?eg-sX3@CO3g9n{6u{J@+0pzJ_m77n!y?>WK zP1ShjE~~Ry)*`DdhDy4O!9YOdgcv^fH8qqp3zjFn`p3?8O^~q=xt$g2-{lX{L z#K|*$;SUErfUrxY*Vd+~NG+MT+Jyd=T7_QJM>&^3oaoAq8{;W`N2>#i&^%ErjnWUD zP-OgIhx-|p7-1Zz=&5kvU?~L!l1tKRYdg4c{>@fbimXm6tJrJ6P-3HP^f8T&aBuznfZe&uR`i@(0|^u{(WEt*8F#_qnHhOQC+QHHDYs%E+KcBM+4nO@*aM$>Q>=`f(!MmVt`)ZBvHBFblxhYhih; zFDFvzgjr@(kl4boVf|e`t&{7FQ1fCv?yO&Q(}d76lq$UdL14KnkLn5`xNCQ){Zr0KG7J`=V%C$$d>Q8PQ{z!Sj=%Y*-gVn`G?e%7&>xnoxWmGHPlF z)0^A_;3C0AmD{#Wd6deMCp!FYv&sm2nNE<{*a*=9?y8esImW%nf;s`!+YIAU|CT(q4o6zpvp7S`gE)&96{BM zD)tIwTAzS(^m?OLrj_*E1c!N#wpwf;(2GU?L%ZEjRy7ktMLPQro!=FlF5TmYuKyc= zLKVO3gn_yH&>H*SvBB;kcLy7p4te+-Z76D=6}jm&0=6rLi;I z8FfBDHJsAqb{2SaD`qu`>2uW#(7ZsD6Q%HNpA*682=DwNl;7gG)P}`(SEy~lGJv#mj| z`@Rn+g|7ZRT$4COItQwMYykzgWV_EWl;<%CQp=Y9=a;uf5jn7qJAi_UCFRxLP)7vo zzsgg}pF{B;ub8wI^P;-~k1k{`gdj~@j?=yzyvlaT~9s+P~kbT(~A%IVOH*Ra8Y zA=;^sNe4()2{#e;ku1JS4|Op9%kW54-M5sp4a`ZS!mSXqc+NcYmL=W?Y5|DPdEW)|SI*4W%2jZ!_@^=_1S>v0V;(~A;#gmIy9xYq^=EcmLJrz*vpWkIct zk1-qVpHa>Nga`w9vwg`n)c*p(@es3D6Dx0~WQ!SYh#~YZZ2Li*P-bHp1Vx?n7YjddvA8pEn>c z1lwkWcE>-I&u2C>Nz9gvCo*GkO_T#4b3YUNyE{dFh;+|6#178;4zcN!{7^RH|JkK_ zp!%q^d0WOmGxXaNn3muRK^t~AV8ezDB8oKqvS*u?R=p?rGQ{*MTX8_Ud@y^KftFRv9| zRhxy=L(svqWoc;|WTV7Dm{Odq5H@IQ)|rxdIO#yWrUoN6YzRvn$NXLpfp7*mq0>x- zP0I)YYF#(Ao*GL#j9J5uL|-aT$Vhy|wmL5%drQlN=)lrH-O8BBhRa!?4%5$=E_c+l z9vaA+rV5NGC>>a5cR-rwXjazWkS|@&1eb(*m0)M{Lhg4bMN(#Q6!fPd(6NAqu7x+s zp46eb_hE-7UAb?*=WMtIt0O*7>*YnB;K&EO{{Z&&Ai5JR!p(P!TWH?c8t~@#bU`Cf z1S>`0fU}CwvE^F3NJ60R2sz4esfoA?`m|}N;3`&-YqHuU(%{Lt==2z!Maq!1yD?UU z3%@1@shO`~GN>N73l06Caz@Fuz9(ajiwM1lRNV*~9R~s(;kwIm9}sc?)Ym)+j1H^k zDp9U95a)Q+i~W47iCUn&r?svXJj(l*^U5y1EDFUn2`D6aOTZ;mC`sHXlwQXy*+y4K%Krxwv_Ftf;rCch@$)XJ{ar$)xcXs?R@Z zOs(qPvJ%4N+u}qtT{Q&Ym5(kw=I@B0U}-fM^H?=@VsmuqYtvpwLwt<14PQZ5EERa8g8?CRx(_B;I843 z>v^wSVeyB0ksQhqaVCo|NaI)X-Y6%X8IEWhDU@NX*rTw0=w^cWIF3Ow&?&}P zf9y%Df)NRuoP?(pT!8-wd#p9SDsrVV+4k%E$l4D5C9t-jD0vtQr$TFRX?t;P{UwWU z$*A+imC3}hjaD;NVh|@Fi8QNvS8$0%-1qb)tEX4l1)fQm(e;HXNPUGWY8V~=-vFJ8 z%HBB~ugqpesVN`3_q#}z)^M%j|IcM#w&XwglOHDjK;;h|oqW*)iIXq<-1IPJNtIHG z6gE^kIj8NK2p;^!<<&~Y42A|Sx}eHXkX7Ta0&xq@1Xr+l63u3Vgu%r4 z^3PoXJ%AgdmVx>|FVtClVzXF``&C8V?+h&f`cv#gtu0c?GQ3!8e^9t^b8iO;igzU7 z7rsm69-#G8Gb1)@;>onsTpqp;$fFOLxuB%65zR^|QRNZ>WA2@ZT}(Qp!Oo(|4JS~r z=dKOIs;MG8@V@pMn*>zX-CUmRaa-qQEHgu*(io;{y}n_^xnkVg3x*Yk?`AH*P;K}e znYjVmD7m8d7~obVrnXpAo{NvNd2^1J#~rWf$CPhfdgCnNqFV5>hsoL}kzTK7`xu)7 z(Bo?c(iJP+ZW^L;qRvHLAJ-PFM6zaupEut$;5c1^R8u>PVh(tZTI;yk2*( z@I7co{hMAGv3FX$@7weP{-e^lCxwPk1sD1~dU@;s`1oCscf4edu|H6gu~4TSe5WGe z%J%i+Uzva!JmR%5vM&sZu-584cYYHv?0otXV0?fl%TZN~|79Q*ZNh;D>|@s6781tX zve?1?tR((onKpCg2YLe&64qBh-=eqx5NU&H@7%n7z%G)mmxcN#W}IFc@!$HatBREU zVz5myBO`c-lcfy@;g_H?r*`cNZLUH$HCrhGMch-!bz^^f!gEgZ0ZdZfA163Y0Rx>} zF#B%r5DK(18R7Hqx6xzZYK3rBG^v{p9TEf=Km4aI4RZ!|P4J zho4Eu*xp^cUF(a`t|j)wTRsn^C7kDzS)8VdjLrr&@yZ+Mk#X*Ka7MP3W(Qhk(^N?AAyhd+U)bA{{8j9in{KwBUL_5CwK^+!v}rYrqYTCa6Bg zi{?s}7^`69fg07Z{s!_ko9+GS?t{h*a%-(iYKqdhBZ)d}_ZbtyD`ZAk=i1|evv^4N zu!Tn#@#6fZz}S`0cvY9E>2}F#vTyZ7C1gu|%Nm$tf-t*uyk&&*$wf_WAji*?jKMxb z6e8UqVW@`pCNO$=35 z$zqv6J%heIR!4X!MXr3r@Tsef{pAqNn&Qxuf%)yKg(jd|r-BIr`enzHIKpkLkpNcX zu8H2Q1=F`cV)WH7=xkn{_x?C&9_-TJ932*-@+mdmGR}vHFSnu+R;Au5k=-5$tSK?T-hfw(S93gJhA;amvSfF+=_}2wC4GBLcQlTmS1#5ek7+bos^Qw z-}{2{AzHV3h^83K~vKtd?S!0mfCcw$01{`d{dTf%29eW zmU=o#+(ath^u?peYmH~mjVQF*p0i^rxy%fJiDRZybjNFQ4paMHV|DlY5B6sH!M-jt znqXQauQgB}7uMA3w+3M^z;FP(_L)8^i$0XZOP$&y-AyLfE}fhvxr0JLqQN72$7EEO z1ss@Z@+L@?+oV~>TS)=DF6l@tO3LcN*537O6qE~W4{(5ItibVI5&n!TLLn|oSsKZr zgq{vCj0!n2YEANp39r|AY7-}gOP zDo>m&V6A@eEu88enYI5+TGs&CyiuF#a%~S?fNTf2G|u9ql?Xwp6-FYFFtyn{ja|xm zh)FfqN|3m3HrefZW37Z-0Etk)mZr6H9m`SxGA|Ed6d@XxLf9s7mEdDDGA7%zUhKOE z&f}(K~X#cZ~=lzoqkhQLJW4GFb15h+Nih$#2twRHYX7YCNyz_|KfnGQJ6PtH{ zz`o*c?tBrQ?PI9mOXmiL*{TX|7uTS+*zQ=2fuO7V@TwKX{Mcy!T*L9Qe}dqCv@aVj ziJUc$cnIF6<96@~`S0FWwucBQ>eF0nWbD{oIIpZU6n9W7X?dk|iKALVMKQ80I+htt zTTXwn)Yd5orlReIG3@JJK?3v2T<9#fk(xp;W$-B!f#cUWSS;_<_TKzHL@1x&zMwIM2wSDtuhgl=Sz!+w(~6RAh%wu#M>Sm2gUdp{yL{tb zAmL3dL$lH{e>?KdL2kpp+?6ZWqx-sj%=}4mwmS&y1ELpNtXR3+P=KSk6pjmQ!}0N)(2NciwkK zX7@P#!vUqkkTpIY=}Fnw%&W|m8=#v1{KMl&_jWo9Ga$Riz;8@iZeO@cm|~XU?aS(jm=#^Y)IJ=Dp4K zpRrlrKx{KrnnQVkNW{RSn&P|8x#jsL#T9Q^RX<_L_JimOBSSUS+WoyLzDB^p8_Ti< zmF0DPig0e%b4^j91$BqL#IHPZE!|)rY-rOh;aN&w8#$|oIO4jtTXyWv69rn06lWI& z4i%aIZA_4LY*CiuNAN{$6>fk0zfv1bc%pcF)OHNX>Y5$;MB&XKPE_Vo00a>Up{KtX zq$o022O$?W*3Jps3hTmNT<%8Q5@>BFvQv?D6p5Mbw~vb+87D+`502#t;gC%2uVFji zdt%y~eE-9Ta<6OhkTcTM#4w3)*+?JajNomYrM-9#Wt7P}EM_oyZ%{0h^1gnXm){k0 zA8OYDVPubk(j+gP=qBa&U3Fpb6Tu-S7O@lF5a@`X6?aP*86RI?n8kTXr4QP8Lj8R~ zziYX`mL_eyHj8AaNeovhkNc!%ZE7&RoQ^iVSGb|BcP`{v?em`r)6VF+6!&D{lNiwU zZH;IDS7D6rO(LTZSVn?QsOh&@6=h0zP7MbhZciExaXAuG7P^{+G^k!Ew1(O=HmLy{ z+TVhne~>K(NS^~zqL;XW0hHwDZbpQh;9Cf9Os!NrT+xVYJ7UaQK(=DE*WvZsZR^%y zz2jQK`frqTc3W;g2VKq-aXKrTq~Rv1LMjIHFyj0eKL70`G?~Pso4LOWm2s+W$=-VA zEpq=a{Z=fOoK4?;2N`ZEN-Y1ih*UUKL!5y5F|kyhmSSiFm$7*>ufDd?;Jk(pU|a*r zJ$oFDZ%31-AmDqL96Z^=P(GQ1ce#b%NOLFCn@d3Tw-yoPWwQc0HiV4AQ*1#@N>-o| zyZ}#!p}V?Onfwf-6#$Vq-5a@J|1N%}lD;9oxLP*%!Ice-0^~)-MOAarF>HqUTlKMiUx4I z@BkZgtcq5p854gg^bgRR814~&G*mO=TwsQ}vp#3D|%Sayz%3yCsB=v$ScTIL_xd4U}-n zC)ZMxnY$4KmfL1zPeK{iq#B=6OVR#IPe72OW@3KL4SPt?l`FG&Bx3D@ZvOGme)iNg z3iOcx9m@25q%44E$L@4f_Z)Ky!7*Z>7)wQ=h+DnED`ttIbGou-pUT}9-})^$@^;YR zUimqPrIL?dVKgJV1Y{;gNx!ucWM@hKOJ@jHMSwhDaAKoVYdCO|EkU-HtYMo%(}JfA zHOjN7)Ii3!k%>G6c#dv3{n)HB_9Alc!G-@7H=(Wx^of;0NzG6k$e4x1BfW9r4|Dh2 z!s}QRFF%AWRwzayh90yPPf8_CNGYTv`T6Z72Fow5D^;!n$;=}8IN%f60waLEgQi-j zI}jBohk#hY>|Wk&bu;^aHu~z!f|4PGo*SiG;~DN-DPve8N-@S*hoMc!MU!51I*PCO z->%vm3Iy8nOK&atPrJF&o`hO=lE+0*$Dw065jYl!q^P+_S%m%qkC%Nt;|by?D84kq zz}^ta5fN!f6s)*o{x_Bc+Y3rwba14=?Ay#?`>awdOOa3C-PK*IZH(T$q{MDm0q`FH zdE!zQ)Yub3@B38Q3-B4#3Fisl{&)!dcuE{{B*13>f3Td5@siXoqphsgE_6R;oS=}Y zXzKSK@BY(|To8>%@$*XZ{$tm%yR8c3-jk0etjW3trGYcr18z37XoO|J=Gruo55&A( zVJ$W4IKimQR^vX1deuXm6!udA`VR{m{?j|XD12)sJc9^vvXi3BtV@3yw=<{Ig+CKV zG31NL_u%2b^@_IZz9BE zlQN)z$ID&~;TO5rCLGP>H+m6c8Xh`T#gD{Xi}4{v<1RbUYZiusFkKS~Ln;HJIO@%x zm}9mb_1&!x{s#7~*sO|rImM-(OSjX>O-_8i&B`kds0<3Kk#g8@Y+53xSLZwgN@}8X z*}UFKRbSW-;0Q!gFYpCAIxH~cSF2ed)@>BX{9>5Rv>;7TB&eM1A?zn86-H669o%y3 zNZ#gRlc#Ub4a5N;c37HZe}>x9xLoCJP565UcNvL2{{+d*oGUeVeGousp-cxiZd~yM zv*IlyVY=apHj;ZkW>b%X+QJEpzg~Jj*4hJEPE!aaP;V!7`1OI}{vP_B5#iAOsCsL_ zXmgr^EG`8mlj;USAUI$byr4A+_tKh2)oK#Oy( z5d9g+E<@-d@sjzrM0ol=tpy~daTeI&uQM!={0)iVi0?kQrT1j60vdga*<&`5SC>Qm zD=w3Scv}8_#x{@C3Ykq`pit^5Xt9aBRXER>3}q{SZ%H3s=Dyur-su9DXo&ZWax5~H z7v0mRTY!|}w^;aJQHSo3X{k2CE5O}bGZoBagC!h%y1m~M{*{giIJmk&Lh1`Ii_i>US9Zf9iYvm))_}oZ$_HjH< z>kqcgJ8DQ5wAu}=Ri#ym80KneXngC)@FE9?XI(&-hy^*cC@eIrQ~mjF>G1R$=Jjc% z9<3S@$CD#-$o9DONr|Z7700;Nsuq;K+^BcE_KzJE`3(Ah154dJQOI$QC<6camh)_)_M%?ZtXv;+NeGS-Qlk$)#j_9GPdZ+V0+v9w>hrLvJDw5zi)_FpXa zrD8jISI5AYzJbj)x}lH1jO27Qg%{}@7m&wQW6N}p)j6(YP}TI!mSBKLuieL3EFkYc zMt4tH45HvB(3D9|826-(giC*qjjL}J53O&C?t&!e2FM@fSdhiDuw@=0k!Iz9X<^RXVULvQH#_a9%ia_4T@*MNogzViM*I-|eI;PJliC=*-VdWBbkKT1;64D3$UC5aLzklJ-Oz4EP2^1Rk zG-P(#k0$Pu*Hu;eF`}4W%;4KpKliwf3CM`f#_eYW{C)aw(J7VHaPjK}>ZbJ8LD+K( z4`QpNW{bqg-KFSs6Gk8v1z1BFe>b$84XQZJL&)`_^U4`JSdU9|)Z-U5$Zy3&Izqwb zdrxHF*eeJ)pREiV*BxIDN}$p||1t&TO+=0GG}C<6s^_VTTsB;g^RKiY1?% zli3XU*t?|uaOhjS-|25{!0)qkY5l~pp?M@72dQ`RpS5izhe3G?RC+1nZQn8Nl}s$K z2s0PXoUy0#=J>V$Sai0SMJc8KRn@kTP@;MgmR+5|dAm&kgkZnC%?fP^Q3Ka!xF>hM zNARm`4toTQa1&(~oIN~n%yWM^!*ZPaLNIbOXk*0_}* znv(=&Q@8p)qIduk;IkMgU^JmYt2-W8MJFIvrQ7AHzz8cdweq5%mm*9~mZp2cLQ8oF z-tcmEYKzCX2x5=Wmi?*`+eTD{7`)n6VmeCU-R5_R)Ib}E#4Y7SSX_}L-DuC9 z{1nG|qf&jMwto>GH(qf_!8EEbuU4M51DC_K=;bbYW@P=O_zV1(szc!uv+&Gw;KRtQLhBGxxiJZM8Q8H z%FM3S}C2P(6w44W*VKT2XqcfF<4an&XMo+7{&`J&s;L0K z4R*TeBr)maH+Y2B^P%`H`#GLvxd21PEZQf^p}sQcV{t4|;WEZpQUWji?V3xu)fu-z zT-2ZH+(7Izcf^wXDnDSE#y5PSR|{JIvk#=JtC@yxay>HsQ~9PqWm8~xDaRe0k|eS2 zD*i>HW-YT9-bdy&eWd z)z5#=m(i>anxBUP-*p2bdkR9gFNT?Ule<|+e-J#zL&e_>8vWi|UYDQe3fa{82e0uX z2#yCNB5?On%CR7549A?Btiz8eadV;IC@Np26=ACfbkC`ZqFw%W&_Gkxy`^UVb&Z-9 zW*PfU5dYiE5n`^sCH5){7raHKw%$y9DBiNZ1c=(bWOUP^lG>p?*Z&+Po4ZHR=AeL* zR71@1Su4k(h2&`%Z#5$r^lY1a#~Wo$U4e9=pG8nefEbWq6k-XG>1|)LmMxiYS3zDI z%xcITh=58CVa1^^Ga2z3;w8KS?ALAxruHq?)SS^WdVFrbPrNme$n}(KCx4M}rysJ9 z0x$rk6pzx1vNInMs4cq&eJSVaLORw%*#11wxpNRu(cVsl!ZOSJpG0oT&QP;geSMLF zX8(6j^JE&P(rRyG)(Fc3tOdSDK7fdo?)(IRNXvA=?z;DcB*g!UHaH!JTC$-x?}_Q) zQj#%*VmRA4|E@rn+LFuB_?ifXp)qs-e`$lg;s>UtSsNVoB$ML>^ja(@xNuyBT<-dGR(8)Jdabj zG($zjmFc7VjpS+xrB8R$PxQ_caA8ZSt-UuzL*UXdyy~&b<=l8>j9L2a=Y-K+h_T!E4O5UOQgd&X%w(YkFdK;eGIQfT>Ii@I z+vxid5H;JauVubt{Dm!x+~?q)T0A1aw4SiGh3d(Zcu~J10WBgRT*!+&$)>u~y!ls0 zm-H1)Ew{VTT@>1{gUFJ7ex`Z=HQsE<9H=lcoSZK+;MD#Yenk+PzHcQlh5O<8^uAB_ zCp>*U`&nR$Bfp23swoCcn@K`>QS|T_jqVj$8O=8=gSOma3tPh*lg9lsDWAN}n8cv) zA7NuzP^~h2g?TaeC`JLKv>SVjjBuGXo1_zeqb)I3cl^$mXS)3-=VhGiqi(N}?QJFO zY3ZsrOYl)g!J6Na0z0ws`Dl@0pZl}}n&MgtIGLYM)eDUBvSPS~MZCfNJDoG2Y!dHAmi_2kBi*PjJsK<$dIQ@kY7PboS z#+c!Ir`Eo_=0|A{nBH%4nc|!9CYeUCgV&1afY|1Issr6OGn;n<)uhcHO}~v3{{Gk2 zQ49aNB?{)RYAQjob+I8i2w={tBo^$=N>eu;5kIDEYm`44?p7!Lb56tnJqm?GWmy+PrGzOE?|LqNA!;;#%}at_;OTe^J;~b)nWqIQ*s%e;pdK?_J?~LvHAcpfB}K z|79nIm0`}IpO0t#;yc>@x>pn(W&ftjzCN`3!+Vzfs{@jYwAzm;xdZ*?9AVtyo=NEN z27CUDrX~y3{OPC&n)%X|e<%Oe0IDmeG-6~6wo#xc8+W@WcQaW5zO*_aCV#MR^ZiK3 zu5>LlS*w))2ZRy}j9@O+p7@yadLERL@cLHP?5L1zL_|toq%NtdFCHp*CF1<$V?G%+h^B6N`|2&-?AHtLA>Rb)ko<|wDX zv4onW(6GY!De*dz_bS)hg}!oUSluM2m;BDXPAsU9gvx~nk?KT|n@vmC-vKuavSo%4 zmG9pCkDwDixmLlyRl_sT(^bcugxHHD4E|zU3QNb%c|vSzZIybKmw~Q!HQ%Y{Zc7gj zm~HgFn+S$g55pi05@Wz)j!81yPqG#{-l)+qW^l*!`ovaNv;pLi_81h@v$-oFz0^e;$J3m8-wm4#2h5qML) zr&@LT(ks{47pFqsp)eCQcEO3S+D#$OwZ;HS*qP{y(pHGeYU~8%lX#=i_m!sgNG}$g z|5-2fdo7>5ma*cpF;=@wk~^&vx&AX;YC-y|)tIu=F5M*u)Ozb-clM_o>Lj&Xj){+q?@p>d+cfjoSI3duTQT`IqoYFa%A`-rfUeLl zhSY31Yv76Tx577GKODrK0{6evv}7qkZDYSejxO4FWFu-B0zyLO3^Iru zSjqGdbMbrhpgLaj@8sy85n)bjRky*Dm~zqSCJ6L4a1O08V1=Ur&NqMA2fsHEH6 zAIbXOIY=wu)N25(841c|<9IdIS5lsTk)2fpnv(G26rW6+o}<=OTXof`vQiaY{L>mA z%66SxR4u}Tvo9vn_b&m%(g7{>#BzhYb?xFRd+uNl8lpblLG-PVIai#k2szM!{OsFE zDo7tuj*Bn=e8#`moUfAI%2_IkyHb?pjAwWW^?k&;M{@IUR>1xf1=gtL4S6>K<=2#! zbF`GV!Lad!90uNqXbB0OILj&4YaG|Wu}AXisb1%c9a$b}yNA;TGxRxR2UzqOq`pB8 znDgBR4U7XkhJrQ@ROfCtCSaJSVuJ;4;6KMeHfc7ECjg9-{0h5Lv>p|7lLSR4G5ra+ z@crd_lHP*gj}^wd>xag*PoGkcCIQ+vhbRQ|xL}pqVsA+)`iH5{zEMwEBCz}aR$7%@ zwYIA!crIck`y?+d(?7{yg*763(~QoBsbUI5CQXwyOuexs5LZ*II8c}u(8DpUV*B}h z@)Z)$z-nQ^iF=2aH8}-=Gb+^jWUY$r1ugNhsVts&Ixv?~@0m+?$oiE)R~Krho{OnV z^~EKTu@hQFwG?r>+BbmQLaedMngd=<=G+txhmVlc7{Nh=#r8CMAI-tBd$JxQvC~@- z*c#qR3wA>69>D1%>Um80rnvogsFzA1ZGXAD)w6tYzy$t@GzVX6ONZ1kYOyrNC&?G$ zcKoi5LDpXhu1v0M0A}=u+s;{qc8f_su*w`0W9L~aWAe{~=f!5Z zK7pB!4-xj%=jrvV=9;n&MHPo1mG9q8##C*cqY6Rt6I3oCZ#eDc zuR?4+3nI63X=oXK<-;n^PPLc|sXAB|*r%a@u1G4$ zH|74X<5QAPj_DBWVG4JFt9SoRP%CI}^uLe!QJVCnjFEhKu7lDELJYMCT`DxWpKvJ8 zeKkYITprQcUie=Na&r~RcxE&xVDSInV;mVXQd^8jpcsY* zx5N%u01I6hl;JiHE^vT+K*Yu53D_U|Z*yYRDxDvL%tpXRjSkS3kOXMj#2iO{`)J*r zR0R46Y(>gWuuDA~b_Z6x-sHCG6J8QO?!?XM>yXkK;{^L9M*-3PP9$_h9r}})RpBmA zE419VIgE7#E>(k68}t@jJgD}?^mh^EwGF;Kv`2NjTe!cLNM$zP`9a73-1cmh0pk%| zYDK%EqbKr?Rf`nw0XzEu13~haE9GPb*EAT^l<>Jmx$kS_lMBNRvKX(xo2*GAbc_VT zBS?WclUCgvhEjs|+#~Jd6;3YBsR2gr0(VDM2g2zmgYwOG%Z1^*T=bNsv!9t8gb9M< z%!eI}Kxpkuu9J74zmo(&%LJJa(Mf)?4@8HnB`*JW&v%LOnWVa)Ia7Yrk||pFq4F;!!-eF4`!3(tkFW7tb&&AG z=p#81C=M=uC_h-(Gc;M6W=2?-eq6e4uaB3fZmtDLqIYw(gKtd&8qZ1yRbY!5g6|Yl z`c}OZ%7b(C-y}%{rBU8R)QQi%V>CVw4L`h~l&Y36bHjC1?b6asi|lG6M)nGE9R+>& z8E(UXLBvKdU1S3`RiI6jMp}VtF33%}H}OV3H#uPaft-%iH_#}^Qt=3Q2gn{Q^y8~i z%SoOkpsLDz{5z9k*Q{zlt^osWZkb`Qbw1XXSelAqlDRqulRJw8zGPnQ8>BDTW8reX z$57c|#RVFDngFhZ#Aoq~$@d^`%fS3&!DqIEl}AUSUynYxz*?k9)aQJ`k0Lm?djbs( zcltT`fB2(VO)H}UnkQE+CoIuUnxzj?*s2Yg@!s#1=vA(9*%_JfQj@CIn*UlrHK1%O zwnx)*D>M&Y$Me0NMJYVUEm{n$K&SnX(Y-9kMZYpJ3jo99a1Q7tBH* zW2<19Ry!mRiTN{g^}|39E|l^O@no}hYhz^TVe}8^}#sg?YilGhltLj5qY2eSXrHL4aCM7X$e_Tm~z$0k*x83a9)=8 zGu|u3yiF_lxY&jtXX|*hoZ8K>*7B2%NbC9$%c#@X4ExD>9$B7K_b`jsknA4MigLWl zw}k-@zbx#v@p**(qBK|94U3^Bu^_2@tccq&OgV3nYwBNWKXS_VGYdz3+P`{EV>yuC zvizks|7-<3_jz_iQwJOdi-XWOUdUbCf!Q1nc9D1L)-pB{vbNM)U@XXyo8jPDd)#?JOuRT&U-XMH=;JNKW*u!!?lFHEHLg`>U_S31K{v29kHU}=;Fa!pXLBB zXy~cJRc&17NSO^laxh>)lX~tJ%c0n4ZRwPUwUO6MdMdYSB0{+*8RbP-&(w3F zW^nIlhXNWGvv@g*e$h9xs?kVE-c=FHRP}hYnV>|98$2z4|3^40{K0E##g|115)u0?>yq$zt6WG!9p3d(fw8HncNwj=*A(ReqMJ<)c+A_K7|7!%c7$lpY~kBp(4lpH1fEVzA>wyQyHF8B=q zZFrACEsH}&H`8$QCMyg?-eS>&>K%Jkm0CI~nH@qsz{?v7wHexy-2ztw0v5wtDp;QD zrRlfZ^inA%ls+9Xtba|#u>&q(9sT4^GIoaGkCJ`x_RThFlU)z2%DWLq1iT2kZO^^i zQRgXhvJFtahzj+?jnU(G9h$C@u>otiWLC<2-i1ry6sPiLse9{bh1t`S6m6!RQe@!! zdk~?*;4mSFURJD^iefM`9KJ#=I>!SoTn$q5A%^&D6yqPPhQql2TeU>$J1*ir63awfN%2ugA@V%t2`GXr=d6K{GOJ(q-0~|*Z zvg1DNBXxjz6C2=TD_gPKYm3UumIKHEO0ril-qL)-e&dx`VS(V{;HMNHBn3rQ${{bR zyFTWQn609~3i$dd{lvtyj_Lg4b6j|0=&#aEtf*ZA^+g>`F+JQ8^h#RY;%L^bprH)S z3p*(+D#w)}#7b5@&(ZCrG}-G~SQz@j6?z{R13nww>=s3kn@Q)%%U)S2?4e}*TlY}D z`+FDBr%}lAWBc=>pKH^lDMi(D;T;KN)rWADcRf(qyCx+ln_c3wOyaMxJEc zedFKY04AYhe=kjvd99$Jb7d4sI;P@h>#wR*G7s};5}ltB{RJ5D&)%f&qT+E9Z?jU0 zRl$^~&dsfi|6fg3N{wg=PjIkB%qjBQfp-OZoP@U#cp{|Z&@91rfzdXd#&!}V{UoB! zKV(I!>iTS>B+CwI>B?2ogyqe0tVwfo(dslEla1WuPq%Cb?cEugUYg1umE1W^JVjuY z^)wEpoM)p>voS@<_$Cit-t;#rA?O*p*!8rA1-^#%^a z@{Nmi%i>1`bvT^(B9&l7j<+1zATPn${aJEsrcgIGY`B4Lc)ZTUx01%y!ZxfUqJD3W z`0SE?*|`p;e^-n`Gghy|<~cybe0kl~0Mu}viD8$o`yu7!+>}DE8jCU#!l)93?VIAxD>?3^xiScSqN^zQ73p#h3Ax23z_lMceysTeryRitBRfF&ZdTQBN) zkgMYq-+X%>Z>?~cU;fXS_#rpW^mKVj0+}#I0X1+Z?jk5-GqGWoU z$h=KnNiU%6D@>YxKB9Kly8nUf5XVW3&L{;;JNaI;^WHoczX@Ulkm;Oi_YrL^Qoqe~ zzu1YPNKe@$j%xDSq_pOoli{n2X@~Yy!Jy3O^m`@ZFDfql4HZk@b}Rj*(`4SRhAM|)JvhWpdT7P&g<*JMicpXRlP&XS9ZLr+dFnN0b zz5n1z$hWqLN7QHm|E%f%ehg`XtX&K+3vJPSR=0!y(4J>onl6%ONXyavLgbU8bWR1m z;Q8BX$<@MMhwSYi%)c)5?h^tDU-2T;b$?scopxFc*%U%)D>!x_DOEeLR938ihjN=3 zJP=~3ZlVAvVrnqz9-4ZP@k^Laj6C+27>^5S>&M-1^N6x8&g^CZpKF-1OZ%SF6TQy_ z6Hm=vHhI>cKVbZWcm*6fq~w5uF9-ab0PsXn3}lMDu?S2meRTl&N*KnYkp1)j+f=5B z+8SfFIQAy<&^Q-iA$lh~(*W}!?N91*GSRJMNV?sGIR-B{E*qozvz^6Z621L%&#`a8 zwyu6ydYo>-Qswxi2^?>OLsghXdFB~d8%zdJ`HDriRT9GuId~@=9%sT|im{L3&sG#o z@i@b^Z(h+Y=JMsVj|DXAj^#5pw-cA%=n~u#Xfy-^kKhk!S7H4w?iLNsl#xoA>zl~! zvWe#p23>SiL=ur`h70Aun5jv$GrcbS0Fj&~DlF-{K&2vP5D7bMlXq?DaOk-%?G896 znLg|vp)u&hkZ&}`>N>McmB~P|QOC&GYx|sl9S(U*fj%2hrbZbAjU&~|l6Bs(iIEwiN`Cnh zfNGfu_#*ezG!&l$>^z2Q;tSUsqh);o4S3^&_nHNA!VQ?4(K;guYe1nXv~5nPl#j8} ztS}YVr3B^tJUA%w=(G&t(Bl*hmz{H;o&qEz#U+gYI*M-<3|icunACX^Db|pIwgiB{ zM=|W4>=dg)>v|9*Ytt9f@H06(Yk-vp1gBkYokh4SO!a^!0uZ&LduZtEPf6xU14>go zwIH(ilU(f(+t!9p1bydE1r5r*_Q7iov7m{j5#0<`*Q^GKFg0DWU6|y69V|v{PntB^ zU!(54{ij5c)#SNkG|geqWYS8n>Q(_0vJNmVW==J`TdU@eEuPg+|E=iw!gKy zyYKSsr{{r#8ya129{nEfPakF=?@i1BG+XGMwr1pK)=o+ef~QAc90rR|p->E|cZYCL z*BPx1#}whWkEVR|WW&%062Qgf6t5Ks)VZ1m!q(>nW4de>A_??V8aRw+XsHD$Ltm6) zN2C4-N^s~E71tsFgO}XQ+isqE7SKgqjEs_wvWn9W8xO(Qyx)5}3`DGjbpz0ay8fZ5 zFd%Z_7B+$%LY>T*?eL%}-@FskouO7ctVh41yTn3C1YGPc*Z}qOsXR$yES$L3hWzTj z*WD#79(Dm6Bb=|YYP|a-D;oF;T5x%-Zsoyy{P2GEZ?cVetq`YM%X*V34`6u&27Lb? zU;1|i`nzBV$zT%!SG$SBCm>J}gNF1Dk#i12>S6K?QrwRAFUPN&7sJUDOiijW78`UO zOc7u%SDB^wN22*e6S$lAuMAEm_^}dzI=(9Ea#m&6)h3bVxy6G>R|7%9#A{CyGjaF*Z6k!seK3wsH zcUcSHzZ$~5aX;k-S)@?kqI={xjhilrr@g-%+4$tX2cf`W@}?AIyN&q*%+-zO|9@8x z9fc0quz{%)Z8m)h)$p?g@d)rB!dYsf+vWRa99+w(Vt|lHd`$)3oDj- z#T|LK;SW*isqR)xPuiS@sB*Djs9*bj_h9U|ljSf2CLV4Fj(~&nNHwBq^{c-vX6f1* zg?hrNuYKzb^0PT~GqZNb0@6L8oePs1l(!v$+;Y0W9fn zoOenYA4I&SB!sLrxb=by(EhnJcM6Ev>v4R^uaY=CDtbmXHGxcMFp_9! zpx=0F{O(ROQs?&EvEO0{AHgG}SDwuNywsZe)9XF5MrbM?WCgF=p>q83(-n=w^d`u! z(NtR7AKiEmZXyz+guY!w2BlRgm+oHFp|Lz@rkji{e8^m6EkvBa@tAiakrQW%&*5_O z+DMYS6xPe+QhZ;(-!6NLZZLs^k{>}k(HpoHm!bf@ABnMuJ{86&ofJJ*`F|`24hA{i zZdB4((Gnf0xa29>Z)j$WvRD1E!Io%T$mPCX#_>Z zi+@Cu%vM!?j@hr=%B)0JU6Ag(4&aZnIg^%)X*V$L@K%bH!Z=4thYQIZdD$*L)%AYD zBTq;6vs#6SpR>cSn3Uj{p^?&TtN+-QmR!?wYq-R<_GCtN2!O^1v#?0Bel`)wh(~Fq zh=#wvK7i5zY}DyU*cqymg0tq#Rz+fvFr7Nq6{+ufunEI)yGJ_za+JR!%;}NnvY2}; z%SOpx`%3sK-?gqOi8=>+*wioTUFlUUDu&I}e^gg)=5{+_y`FnPL3^>3|n~h@s<4(gLcXo}Sxee{j}>=XGv5m`rSTQ>>M8lQW>on?rp>FT#j5&do*+QvG_T>wlMjCC#C+5 z&6pUB`#5*g2ANzerG_{H;5VRPk)MF%;@BI_F$?!Ee068XkB)9a%vK_b^^e}7P5P9z zSNhcaIx^IZ$0MBrjt75syFlh&P#O<(1gQ>5kr?h*Q~Z&f8)^Pay}SB=F+YJsgGJJW zh8v1t0rv3ZYD7Oh(-%*ivJn4$>@L82*a+g(F4C6M0bej78Ho>mJR2GRVXX8fBQ93c8k)Nys@oJGqPBD=uOioQ`8{@=MCiq6OJAPm1 zqN4w0O0Ql}c&hP$`IiMdyZp9(BH123__MpssX9$b0^ewr2Fa|`sH40l#NNGvqGWg? z+78JCCDO<2RbX%65lAVR#gogF&#wjJ%xK*}=!96KeirQ^8KOid*nF;vRgj65hf z!~>*o@cl(Nvc+R5bBQ){JNP<=SM{C^j&aI}lGU;IOZnBFtQRwocVW*(yi4(0v=^rL zB9TQYl-$MN8)tdvB}C3-eY?ii5i;2w+MYPny-6pvr8&;Sih#|#rDl8+(PR<3E1r$3 zg{{Z-&pCzjZZ)fhYYnW&iSE3l`LdGW7aF{Ag~SQt<{7!hc8rndvNn-F(f)e3!b*TJ zSEjGmrXqSSUURdmn9g^tIR~Fpy{m^)S^BJ9I%30H`IyiA{f^KTLni71h36=EM~R3b z)jnKJf?&@?ql)d&!6U1ti|tq(Tt(L%jdEN(n8$<6)4X1qx$y_NK$H#9vN%hg->v3O zk9tqYwYjkYmHO{oOh{!R>I#XtX-UqA0l3LvNhTOpT_!it#wk}5aZHJr z3a2pv3#R>6E&;{1*hYz`V9B4Fwfb5dGHz1NmkKUZdlEkOl}Swpy!q~n zAH%R84P9BeO(;Fi^-Dx3YdbAF>z?&i+_E>-1LxYTkF(Sj+b?!Q=X6=yWsSunS)`^- zWZ;_^o44?rF$G%+5+I6*XOHS%-y#yLTZ~yZA;$^l)8$tYfmt-_BX&RL8RzCH$lnfp zS8IgC9+scpkB;PPL>1Bj>M6nfb3}%T3N*cZ^?=X;9Hcx~M!DmjFBX)j^1N!7LC7=QxG2&1S|H=A;{*I1R; z(7O!e(VIJiXO**358bDtiRgD3Xqm#q7D$f&1^6H=jv8L~0#E^qCc;6WMC;e}D(4p&M_D0BwnT4N(Vb2s2}UNB%GreE5v(=W zma<6%E40b53K6{e@|*=2xN|lj?yHjX#V!TGlfyp<_$53LO*C)vv!iQ_+YK1nL07-K zEca9sAM7w*c-lcV(qwM%iw|?xZ}er{&U1EvmN(>V=v{V9k52FW6Fxp?isZZbkZnX zirl1{Z>a#P_ooiv?wr9(l8d^WWgPqG*tJpVHT|ff+}B87l0N$#3nI6bv%|CKz;Gc- zN&_5)qOu6<(o*?5BJ)0l^O+3p+Z8!^nU7hUL@yv&PH-eXPRBu~oDTr9dXAxa0M3y1 zP(@>_6pOj5!N)c=}b15Fz!nNG>=@|HxM^2~CZG%qGy+J(L_1RcDB% zs7;^#h!e(5xN%HI4WDUcNKH7M7P#bGj5*ONbz7KbcUMnyjF`!$@w5HjBKb|b|MkD~ z{eLg%v9~J+=;{IZ^|x6C-gk8w#EH`lGd5ytAz4XVl2+tN;4%*?a9c>Us5#%s z+P7?{VA;sa!Di(0CqH?^i%uIkkTLjub_C{VNoz_kC118n-Hf6KZ%B3JlN!a~w6t6U zQcnP0_5*itfw+oo&Sb>#oxWEtb!!Hu@Z<@Hu`CXzYeN2wCp?j$yX}JDy8_GUD|hS^ zcDss9RMM6lX5SzH05qMv+xbF!ZDbp-D!q|yT2Bid4e8gMT8rbE7&ZrkLg!qRF$)f9Z+U&WTyW-A#o2qIPj8p zlz+P{8(`!ZXu$cpODAHMa4IVGW2_+Qr`Xz1!9YDj3Vg;(+!??f#_b1z;U2lcMkGwg@_WGl13!$)997Y zgW2#My02?Q(iQE1n$g*O$R}X7>L6iiqmLx@v&v8~{|~>VWePgyXhk(~;WG(Cz@2*kw8Y*HQZNMw<@N5o>Ohmqtp0~iwd zX4@5w>{<^;YHM!0L5*>n+9t;|?PxT5NYB%NQWr zH**yMoo0*kj;MzJ6kuiwU*HhkzEynt2v@HBy85hp@Z3DuyGX&+NbF504I5tVmJz~a<&3P`N9%ebeoI|bko@D89Y&d`z4Xb|w{rO<2B!A~kO$}r!K3&m8BKi;6oZ^6|ufw;>NijCK zAS!uw)+M`&PnSOde@xx{Y3PliUhbIG+P8+EMrBr_M$99(#(psu2WEt9QpK5|mHr_A z?(zF@#W%?qEZ4xskrEQ3x;9I01~?qlFQH_+^Z;WeI8Uk83FPliw(=diD=L_i3wFny zW?ehLDLR#jN{R)q`YFJ*58^Or&3uK^AYW*;^0g>8<_py!-<*3r+g}N!W`**$fGd=3 zoDU|kOWfbTK4?{&xS|+YmWGK~e=jM{ob)yA+cB)O%NaDy)M?vwb|I{CIolMT%UP2k zazb}N4M|n(<^Fwz<)i~zBeX;o5HT3zdD+Rk)urv2Z=^%d!Bue^EAH;5r-5nm6- zf7H2yb0A~|LaPK}XJra5r-yQI+vpGgLqNR02yZ3j3{%euy#KUxUCZ84U;NSmWi5PF z@f99`g>B9HI>q82!;QVi!sHzP)HU7FX+3xGxJzTfb@|C-%M+PY5|B1{0Weq;;=OZI zMK8 z$mn7YNK1RbTuUM%iV0MUqO+JWhT$?6hFW41sWxv}K-f9WBi(B4zhwHLnAi+mZkqRf z*P6H`{!b_Q1rXlcI>ch$J(U4zu8x5O6USz>l$S)r5%^FSew9+Uaj68yf*KdIl9al* zHxlhKw#fK&Nqr9!x_c8J!v~S{5~n~IvTB{20QtwCgKW! zR5I)L|7)cfP_A1nZIM6GA6v>2%r0%|AjSZ4gd5*Hb1*&1?MZQG*_obQWXfzH{%+Q~ zv0On77@q!wkMqX%QE>HHD+yJfqk-NkY|JUVZ*JsTqebp_hPu|$eX_4A-M^TGB*nmg z?$Ivw7i+U~C^|RAJ80J3t-U|#;!I1ZRAbLQC`jeSWUcD0cP$a(V>Gp64bk4UK#2gb zdkvHe4AXO&@NmI`9a0*ZHzCKZ4r395RZvudBb#0RA3M2`dYUj;Ag&o*(?mxFcF(Af z-0z|-dbsFhqmi`8M}~0uvBexewm^B2Q0)#572alg8Ixyj6 zl2oQLkYo_52^}~|pN*4p-(xC+bgZ8nGg8xV#xH+6;Z$)u*tA#tm`d_9JtUNB+-aPt zrNEoE6xtrkz>Q6S=2Z?xCvTE=lMC)!vTru))=g^2WEwmE21UIAYTwK9rMcLh8(bf6 z01CFXz#K#~LD7CjlW|H}O(xZ{bQUTYVU5%MW=-#!1#ZH#=*x)#3dG6V|JHB4%3h<= zKu>{Gv1Za(sYGRBQk`_ETye)=LmlX$9!9y&;ZwP6#i%g>=KO!4&lNss zY(BzFoG~Vd%Z3a_{q)oIti2@wyA>*a$ z7ZF6O3y>`(zyK|{*DM9)0cp~4;P&gNwtBFa3nO1^u!}&5b>f^ zQ~1GB&~t1uO9r<<63I4?*0bdsbcj$}Ljv|p*db`XDYC26b1S})a}w*e3xrUfC0rRi z@G8BnL2tTS!P=KuHnr{&Q0Cyq5BBI8<$UYq39z_*T(~3)KWbXJ!nE*rDJaI;DT32a zKudA;m7oOw&*YHX)>C#d-ut&jY#r`z03dwQ&-xAj4I=+J519T=^ps_DVgr&j`MA`0?x%jUsa@MHQG-nDEKD2s;*Y6diF6ix|5VfrK0Eci6{n>YE zS?hE*(msa$Dmo3v)UO8jx6O~3ffDh*sgRG=U#A3cu7jbKcYTgHa{ho?X{iL_)KyVX z(K$!xd=hGW>dO)LwLfWKTLYsh)=uvZDhRGQZ&*#?NtYVyg*z%Q@HQsaC5plKFf- z`yh2fdhAURtjtKy$Il2G4Y~rx`0SSNXg>SIB5uamxUA(j`sBrau|c}+#2%*X1dkjK zjE#T@*-64YASBrEF4|OPE$}KW)>5T9X;@)y5DGTy7peolH$gPA3(_*@VpFAefPdGX z7NI5s<{IGhEcV`F1~LrkOrm`(JwBU|+oP*(jv-8BT(5R9P*-V8wI{^!cRKhSy%OpE zGv1K4P>wwobpK5({tqOS&PSXta}6p=v#@v|B8fyqTk%UB=0Zr%>C)plfe7OzTF<)I zuPVQxWI=r&-50${XbW3vMmJL@=!C3o@}t2x<`2AqS#vFK%X~vid^byJJA935VdZ+ z@PH`Nx*cCqW*r~n>C3fAkc)|#RKc8oT&c44&M)86f4c=f#z&RYp!lq3Hi@N@OQ=DL z6dwk=WcL&k!5;0Lh{IwBIib6yf=$0yF3e4ybkB3;^Dam+S!Tao5W*-z!RAeBpk`d1 zUq$;VNv|!#bne?I&&!eAJafFc_1G#B4}0`z%nA85LR0L`UKHTwNFhvlBjo$dhOSkINMkpHo01SLRu(M zvnv(tVB_VBg!PaupBJ)5ZNF#rY=cyVZl(=T7QbiL0DZkkinr74m;kes#3-3-@9jjI zY&Nogb{06(iRx{;hY8z>t%Fq@b1XVVK<`eag+%^%6v}b32X<=F=yu0||DFuu@!42Z^wf*V^{TA3k{3Tgz9oNq z*HNOh@Jj~IZ~?rbOImj5hdxoCOk(|6Ybx-iqo&f{!+Bwqa0|&+8AVR8`qE%06|SmM zfK``!hLBZ*;1~Y`ytrYCynzfA9)9)u(mG#tjp=aOpQCVC3-FIlIOvp=@jktJ&?0Np z9xmEM;sFHI%=?*X=qikAv#j9-RUR5THM9U=Nok&lq4!grZsxm)gsz{K)|85@l@=JE zYBgpn$A=GTO$12?aE(MS+$uOrh_JYus=SwnZC8Mu-rX%x)X6pgaxh6 zTn7g^>i}dP&wpQ_o2qJ^t*u{q-o`?lKouoR6U*xVmA+=37&&`!;{FJ(0BcR+MlQCK()J%4UIL`c%GGLlqI;uxi%kS@a`4 zhi(AFgu>xfp3Sf}PW6?Bhl~kAMuV$eSW;LX$Y5O+y*;PA#lnXk?ror2h4dWJ6fmw( zi|YU0$EbnYs&|L!xJ`}{y`qb!tbqRM1^u()Y!lO7gtEUYk1w3o1*S)bAA#tAjPjb1Hcg#CbWkbKRXKlEAU4t;pL%ftzcqlB;XKU078NL zLESNIBA`w|h6?vn__5?l=&#?(e!?*uY&b%}ThG$m<@*Jg;$_Mv45WJQ`OVmdnh>E=RSsx2>fuLVgVjrOhE zbbyPG#HBfRO_Gg;IM6&)sM~^6;0}z3=e2h}OHs$V=h}#?sid~jnq9p$;f`$e-nTka zEnr+$VH7Gday8hwIVeKA#Dp3DD!*hm$U~;3mzyG2{y(+g{8#S9J+CurBW9Sja%s=ngfT2=BYBXk z36)qd%L66TVd|%VWV5~ zmOH3*!rOID8r0#xq?p_pk}!BWuaK3-nGOQ2c*#nMI30|CqC^TgXnushbC~`92O==4 zTWi)NUe#WnU*rTHKOpQ2c@2?0=Oe`NV_dlh!WbqCYS&T$MaPnEc`Iw=@=!VrKYMp& zZr6|kO8LQ!elOeiI%b_q@5U6i7qK!OkQ?USZybLd!{UDEkc=-5AEBZwba4i!T&8J1 ziF}Fg#aKzI)7^3NM*~xq4vr0`cSCLLBeJp;1)|0S$|u(nVF=e*ISg2qRZ&(4?`esl zJ{pExEPT7gsyO&@XI>6vEU^Yg3AdyEjl&%g=VZqUC6ee0vge6!b<|CSMWYprA-@$E{1WiDrhaqL06i7jJN+9tK7}U+{B=sE+0J$+5dmB zo$(|$46$m!H^6L<&*qoyZFU^-3H#L=NWE?e5`OfU8B>= zV3D?&2~TB^t?nDH>`;cJ2iD~_)6G&(B8YA3YDtx7crdy|Pnb={Y9J4zj`!quCIt|; zVaYfxC)O}KG?;Y<7CrkF>|@HTM{qTdz2PmY`DrofU0WCnzx)k0@38lq2O zy%$fLX44_o0RK-PoaGJOQ~CDRj2vL+5Fx5>D|4&z_Bpy>2-BO|1MI0PZCqY8c|vkW z5^87>gmvg<9zpOwcCArjcQF_jt8V7qO5Yh+CJ^m_cALf_;F78edV)JeMn3(K@|h_R zMgBiUCq$+6u|Q$#>r*alX@>3^7|dy@AEP-IiffM(lAO|@GL)j`O3bCZgeJbj%W~!q zy*$fUj@C)z4Z&5t90r4m%@^^LL;ljH4Us(wEJQN6S1}x_-?5a~bI7Z1m`WSL?Zqz8 zGMy|jdNe7e_|mE6=mV*yB_<4ewL;p3(1RoPehaYKhWGhym2`*zxoa8b383E_?B`Df zGC>p1X5{eLS4&?5oqD>`nsDMK_sK+$kHYALn8_gxAQngiUt#m$aB;&(5c3aZ2_k~?g4iGCpY25zk1 z<68oS&?zp-tL2kS$Al0}hdskWW;ag~6i`p5ll+6c@?`AUWsS@^FvDUN()8dv;EX1r zi^=8RyrRuYkSz;cYs);bqKhVx#!HYef>BBYx2y!j<0?Z8fRI_P2IGq+T%Uu8Ch`SFxX_>dVa zj?Dab`Y~*r|DI@<_8)8KH@Q|vW?C8gj<9`#@Z0tg7;(QIw8R3Oo$pF_U?1%{p*2Q2 zb)(8l^Ea%mN%f$kz<-5J?Im{vVJ2l~lhc=6)smxC0igHuZJzX6(HsDl00Tu*q5m+E zo;n7(_S>@<8+#}WZHEdvh|&7MJgjXNIm>Sw>%g9m)LCoYJycE2FlI^#f)dehfjw4V zIc8nP?c9$cL<#t0ll>P%8m+l5!ql<`Q7s7SX|R(0?+r3P&?BTK%VTwjR8*Wc>62Pz zLBAYKz?kd`@)fGNrs{geuluSx=iq|zMU7Pxpuxx zkc3l<1=>Eq$?^MtGGoM1-MIbrgE=>9IvX5W-l5OIB9)S)7AMVIaa_J8f@q8vR~I~5 zUq>+HQOqCD$Jz&9Jec?hBv9KzNXMlM^!4B!SHmx*MZmM*r2;(>5dsN1LDt1s+oFA-UjCRMLbRhwQX&kK$B7?b(;dP9U zld21mVG~v5^fBsn!iw+L!Xb zc}4SodPx`YpW0$qVtH9?A*gNEfmX-zH(bhDbLe=-;RPf2)HZ-BXIhYi)+CissrYhR zJH%q{pt!~IuaLneLn7OH+GE{Qhwm3I^2FYYZA{hI+%aXU%dJ)*FBT?jMM-tfObvO? zMH4!O6HF|Y+@$_SiCw6lUrSjyj9t*GH`=2Yu~ldMv+V zMSya|%p6*N*B9N)5}F~IZcO~KP?ns`&Mbx0tTzw1X21>r1_Ra?&O~SdO_dlU+dZ)* z^YRTvDlA?+r!ApUO{-tdB#)Or38AG;AF)SK&ij4zzQ!-xSc}0?)Hgh0kgR(Bf-6>s z&0Kb?Dh|3~{=#?;y)$W*M;TD_#wYq&t))+?(}+p49&q(eww31o_vtr64or~-;eZLB z5a3oKz)7Dye;c_E%H#z63{V>DVOHlhLuGc!m81&DvqHKr2%@RRu6ojn8hVzmpKR(aqQ z1}Rld&hy{suu-q6K;c01Blg!WX^Qw??w8?7Lv4#n3AXxT614v8;Rfor`V`pYDw zJbzkttw1pUrG5ClOGCH7f%A`wHntVNnfO~3F?Fc)|V`q zjQIRqJLqE&k6#E8^*BUP_7FiNMD|wI4dSa=QG!)oW|aXII(@y=7$pnHBgSMW)jNrH zls;H$(po|yPENYO+riL}X$$4J@PH~^aMRO(sAl-u zUPq_q*TPGmU!g($k9mPN-h!_$Wu~%5L~#=dqF)Tt7rj_#KfAZ)egxJxIE}Z|INKs& zHE;!@QoS+!)I$%Tq0eJY0Aor)mA)U?Y}gob5|fHuzh%R?mgjYV;V)-w{R+H~_g8XC z67>u*f-f=?=2GA4`lR?)mgbi|@&62ZbS~SQ&g&6c6PD90JdpeLRsV5;Sm`Qf1xXul zP;R5n)wv9&w-;^A$s#j3$?WF&_tlt!9+`$SH_gBBivGY}f$46C{FL)i{v2O`?l_S+ z$UB(3V)L<3A6yMQrFl))L)wTGe@aW;c4>v;h*u#Rf?0ZC_8(aHl?x5;|Uw2}u^NvC=6|SSEN}@U9GH>0HBrs(d#9@g-Hj`QDiPZ2c3# z3+fG_wTI>k2&!Nob0SB`SulhlZ`Rfo=BjS{R%o5q77FxHP&2i3(E=@3hH(UzJ0ngP2*_v$~XVz85{+uO>*=_uZz|6J1&ozXXp&x*E@>aNYKDa}{ zjLx!D^KyzdO#c2wYdy^$w0QOvC8FQw`@u4>Dx;8-%@lHy1##9o%s8}5Pf6sL+B_UTVdbk8+K9l0@$AD1hfp*B4!XfX|6Xr7CTJrDK#Q!H z*wmn`OtC2yCE=Y6^Goe<+K+>LtkTx=49|w&oKzWxHpT$ zHtZk0xjRY#uE*Q5v_MV^-CanVTM1~MXe;A2YZ=H|HE2?6X*gVOm&GPh98`x#qpG z()YgIC0<6XA3O%szv-p*I9G97i6lHjmRb!wxgyJop{PE4qIjjRaYJG+QHySP()SXz zc=!*FuU+^(e;QjRyE|af^>Qk_OAvvTMGU$dC9xhszN%(bmH-!j4p8>{Rc#ll=D`Cbn*s4s~t?!gAP zM2evIw)nM<`S#+>I*V#%+32MF(SXA<`Fc15^0$I*6?F_`)F|2{IiVGnmUeUCiCitVRDS>fK2&bDGXrW@wv}5){$8#cBl!YJFoiM zEUZVx@bU^<6G%uKOga2iPEJjH0{pZ$za|Ge7XG3EwwQAaID}nMbE6B@%CfJdw?R-f zvNn<`eS{lzvE!XL!)|fs(O1WSUd85yU>N`@WQ3?{5u6*zdF|7EzYL359>QU z8;ZkG1(AZx;3JUGs_xpqIm4oFfIGB5;TNb*zy$Fq_3iyXHSHyu^FwV8O3}T`V(}2< zq#Ub;UFOB1dI|YPb&2Cy&2;BcERNt`dE**%S!y| z$<-IZ2mK7;*YG#N_>k5XRMYLEEly1HRz>DDaa|^wp9OJwxzjui=k=ys<+;ayY@{m_ zhg812-7|YbDDLk$eK{Ie86p|`I1F5}}ut<1$StzB#?^aFzBTw55c^1T@FZy7N?=obpR_aOK|it$TdgWpSYcLwLqph z0ED9xjF9y)hBCh4H&aiqy*!^ODt@Y&M?I572|i59;K)OyP>VNbKG?$VUzZV`r+&{Oa6qAQK9(P9?JZHXhD-{} zP?k@G*KWqI=2c@6=Gc6}n3PvBOr!`o9)Y4u^hcUJ+J&Hp<2TU~B6qr+8X_*a3gUI; z>XuL9^?R#f-QDD?+3)}InqhsBO33YIFYET78NYwE1E!wvKRgeo z#Jg0br)cdx6zEm*Jis!>+uofS6bu1Dh2)Sj<~ThbRV+BV2cy`hYXQbxpa{LrCjh`a zIje~j^*7*7xB^cTa+CFOO_GY7eoLyd?w>zC-f?1cWW}cNPHUn|$YjVO*&fv?Ki5D7 zCr2r4K_2?~+L0T5)W9bP-At}5GGzBczy(|&9LkqArk^VLZ5V$Q;pNASS$xcfaunK@ zM76NyvAo;pa|s*jX~(EL{QGQQ0OFe;uTo8IPY^b>!gF)~%@9wy?Qa=4(m2I`o;kIn z=5%}Wv)~q3x>|jc_hLIgv@E37O&78zE4c;*Bm_7kPEnD%^BNmp+{Mke(1d7=)tNvI zg{Asxfz4M!#pwegebV(UP8u?A`?gS0oRkBd*RABnvKv)D@KJ@{1qjW#RG$QKL&B>`g+=exM(>KghyvCs>c4+75K)lvm1J^CYKoo?4dcXUjDR zzc-GM-X{+GYpL?jNm4{g)Fzx4>D_c5B0C^vvDO6NF75Y<$lrG>iO&MzjxG>QD#E3{ z25L#<=GCg^OxF?lYr;|LEc|n*{={5v{&Vv7@F|AACeS1H$G{R4W%OWyF(ia%gH7|( zV}3+MkSGlMJA|mEe0Qlc19~@9+3yTEj|U<=PdkAAKFLUff;(XiQ%@ zv+`x)X6mO-P(Cxp5W#i@+x>0}&hlrvtsd95GJQC1(CX6@5vEWf*?TE;;Ijj%isbd8 zL;WSQBjLbgdqwf;!u2`TH| zWhnE>tnUegJjNi==wcH5&akjVAFKfIz!po~Z{trM2jG|Eykzc;k^5*sFX&O7x1?K4 zB|Aw#-l&9KeE_&1a~S3@lehKI>$(3`j{)k<`ef$09SI8>`RiG`v-Le$LmbV{sQ~*J zz6D-iaduzjO8hW@cBnC*Id*YQI4UiOG{akLc%iSw1ov&eBHD3 z1iUL00Ge3?siw{HVlCgP2Jq>i+P9N3O=k}kgjcx3lcP<`H%eZM;gw8p)8K`C{`U!M zp!SwCKu`(g;Qeq8^xei`#B%r&0=UpJAWnGB8IY22mfA&Wpq4ai`wTKTgwSceyShLy z@XY%=|DmWe=bnNrqxa5n5)V0phGXE5S=Z-%1dk*QTG}4#JGIjF@D<#|(7Z z8%OjOY)I7GWbosP8(QaPcd7DiOCMNHgGQmXUDdc<=-}cy`u>+Co3o+h67b;VwWdPI zM9dlh5Ei;O46pZ$dWV}!+}^uT((*Y!cmJNyq9HeADVL-rB5kX+L8Oll5X1~(%P9}t z4XM0FH%r39&Ao{fBw#z@P7jv`kPmS?w9KkZgV}O3mb;Dhj`>X)3jcg)_11Zn_nn@? zBEJvfe#TDq=6OPmZ#y=nWrk(O-Jq4!LmgN&F&{bE`oys?x|SJP%tg>T{gL>oOENny zAe2Xyavc^rGi{)P3Qw7@dU7s78kWA95VWZD@H0G%ew>#RUBjR&;WXv6`T(f@);GCt zZaTm258*-qi%DbY*;4(2$*rD48v=&A%ld|%38rV=i^Tu)%GY_pqH2133H_qEIZ_PE z?UT@e0mrD6^-W14n639YxrGTPD%mFt^B1Yk!bTNZLZnFAGqu`oD%-?P*jn66&TEjn zVSv4X4G*R6Z!q5lMYzvLoZ^8;3;rw+9C^$TI<3A495c9XiwB$wbD@luT* z$4Jq_PewXvdw<%*KU}b%LCzQ|W!QTtg+-MUBjZq#fER5oas=ir*d}y)@fAoQ<$KtF z1F>(T)%I0n5LV%fizn-fIa8u4Mdm7Z@0HHzk#29bIyPaAIxr2THTE|+$#Xq(hnk&2 z%pzuOIm{$9TB8b>jB@EvB>+Ai@~c+A^9@~+0SJJzJ&Zel%5%aS91OhwA8~#aL?Cd# z=6$@j^ClcdXltP-ZZr!b#vrK1kV|TdYtlbPJg2WZ5}9zZV>Ft#{ooL=8_)FZYBcIW zMvWzY)>$8VQLTuPj=W;Jba|?9Vw{yMazIeNohKTOPECCHaEss=kIGB5qH zLXVX6c!vO*#03)|_Efo6udy}3aWE5uBAJT{WE!@HHOe>ZBA<%AwxUR84Ax>e-h=Ry z04hJr43h3%^#4s`g>MSa2H9HCe26~4eNwRqmSmWVW9Mjt7r6+YE=5nn!PJL)#nfaJ z1oLg5pN?a*pxf2iZMzrC@f2=JRm`l*n^zvV9oG}RNRqY`WIKWgHWfNNBipNeE;G-r zhqLvkx?H(aSKxj&e!d4ASw{)e*@vV^oIIxrxm~P%=$IVZOlWrBN<|vAE7PLTtLm7` zsq%LaR2VHgi^Tln?oaNwaYZDnd=nwo4YKx27(#BqPrN-fV6H?(Sj-!m z56wD7A-udAG;tA8DOi132*0j;ikp-Su^XUh^duvjxFljbH^k((Dmg6I^V*`RwpKpYiss zYGB?$^QwN)og=a4yn@>ElakR4vz)|MbN_(P(9z)wMEzYj>BNhKxobY{2Yx2(A>SZa`M7i*kylSp8)&wxV z3o*7QWHx7y{vEe+W9>p~o&%e*WX(_6{B>9e+A+0vy<#JCHP=2J48`d{G&Bd?q!!Eu z28WIp9b}ZeEw1&rW{Iv3p#}+vo#a|;^d@+<^9bxh7%ZgGYw;zN1yd4|Fp1>Df?b`? z9Gc$X`uJJ3xGS$;wyxU+pAj!~QW%aQkY0Z6T3cffn^nt<2;yx zw4(4tTC-~c%{C^}Xn}2!Q!m1Lufql4oz}Py1A~}L6yo%WLc=|Cmj1t3ou_fuQK(ca z9LR<%iWZ31p0xl5uME`9d`9;>Z}4e|#8r1p9p^esd1N7$Xe&!61ra4)`_S-UGEQ+@ z|C&F?L;1>Uo)u|wi3hCb+GhDb{kP;@b^d6H&RXsOjMZBpmb$N}fa&928Ghe0`*YT| z5w9fQiuS|g4dcyIg)82dymn@rxUnuBQ|9{ zy`cCj%mSoNdt{@{>{I)-t>CSf}#j{nHe*wbjI>a zX(9~Zs)LDj{+WB}ebLTlR@p|ME7288=D3e2wEbg;)>(zs6Il0%$olw1%n@G(0SAZjebCg+-NrDHRtb!;tf{b472T4E7It`i`0*vj~Mz`nZ{?IjVDcheFC2Xv@osH(Y>u=4ka%Go)lFiK| zsIAzudTNxPv<5Kkf;YS#);Qu!Y|t6KfmWUCmz%M4Vy$hjpmhad3GK**7N|1HPYfjun<*m2LcjIkG7 z_@YVM^cC4ffC$mxgDWLb+@)~z);`+94Xuz!2!(~yV7nAw`9^DeY1d)t!e!6va#=@R*YxO=Y zh`0s0AZuHhX&v@`$r3bKkASl`N*hqz;SNqVQK7}9$7N_J9VyX%_Y%^fF17?B06@>s~@ zJ{Qn%q6bJBnf!nsq*b(sS#L=)n*hq41zmouZc)xQeQ|q&FMi{D>?}5a6cfG7Wi_G9 z2ac!j+iGoXAOl+>btxL`3qzeVIY=sG$a^rA`jphjrd-_)DL zy4GU)@Q$D$X&yJj?X?827t{!cKy60Vj9TP}jWm+U=U;GV2d1o&(;y1Hf5-6wQ+xc9 zC7pQlSQRy0Q)r)B1-(1QWHSnhp^Xi(9cibKlKTWYx`zKxfKizcXvB67VXtiXU2rPL zXcBY>D0A!6Iep0p{Id~uWqZp~dksOnY^?VerB}2HgAEiiuz~7BF006e5y6 zFZX_iZ>X?`-_{>i{}@TilNKl>SOHJErcQPUhrxOj&Whs_S~788Q?oH*+`}Y|tBb0F z34Pl-Ql25rg3cSW&6Z9PVZ!1KC)*uVmHj+GtApHJM!x6uCu=1GX6+i5$wwO9`2FJe zk6S$QjQ@O%sW2AW)&L9mOHFmO#e@!;DcIhw&=l865P{b6%DEP`p~;QNy&r@td@d=7 z=!#eGAG$H&8Kc-%&pt&KHG0jqn5sN{HAj8|Y}ltR*G7?~4#uOTgc=@!EQH0NNk1^5 z^2G)kOlH+49pR4|Jyh{LsLw)&%Oq$!u7&Xx zqEUHFq^SpHHkEvMd~ZU2kgSKAQ@?RQ}7QVNjUKxrOa82G0~z<*E}S7Tq3$wcbrFnTMTng}=Ub-rf1@UER2UlU$j0|%)be&d zbiILB{o(D)~}-s0+0Yokwd~cK>lZYb3%0Xqv6cldTI@N;09Aj=upC^x&9MRmFEtW zq1@3GHHTf>m6mEj4a4%H51iI#;d+mwx$F6?@vRaFF{J~_z0nLYJ%hKc`#kC>o6me6 zQDjcNCgx62?RGFD07`pM4753o*^?-`-oee@qycc!dMt@ykpC z9UPEu)gfG3_CoPVbd$l!icc1Sk;k8u5}9fV1UT5lG~+Uc1V$4kX_u2p66OBi<>U=9 zQonW2cm4QDUQh-0&%%0Tfy&7iq)*4m;bV&ux^7Wb=x z?TSz;Ty@-3dS6tmhFE7B%->1P4J(-sd+FJ|X=$P?U06peFuwR>S8Z5Q!;ms z6371+QQ-lg$iYy#+ALmANT^3zk~0|#HA-4Jj9gk(&l>t!ofj+8y4fs)hIJ?d93}+ zxGf>C5TUh_dFZyQThx)juzr;1W@j-JN*J zROG2hno$Qs;xL{l$je*}vS|w$<`Mu5w*NVoJQ0$sOVv<~gRz>mgle?$xi|oX)T~irYEOikmfpT!p3{5JKh}wf~ew06{ZJCTjy;9Yn8M{(i>etSD zMsLnBAzp|t^I7tc{2u(lZ@GZKa@X%U(FqCr=Im9_UHxr^0Z0H#q{=0jSCOMACS|~h zDL@zWh)xJkRbYf^wV|@qIw1R!h!<6>inK@UyXn5r(=w{CR?8_JdV$ys8h`|Z)Xc|T z#F{tK8YHZO-@CeH<6O~&V8k$#N;Ub?HSth#@O}AOP0_#Sv^S`U__BxZ(G_pXmV2 zTtGG?7eW-*3;=PzDu>_KW1>~LG~*i%KLlFuZz#h--__sr`I6;;P6SgX18)w~!Wj7; z-AbCBog+9Acw3EGA#@cA)rab-N3J}t8m-SK$=tm5MUd9DKLC~pKKP-Rx^}NMX}tN3 z?sh1=o=uY$UHClx+Z$>}x7t#UTbdIDJu0rITB*-~EH)C!j4pQL4*K8vlkbluiR+%g zpGuG1Qc0wx@FRF05jlY_yQEf}OttYj*Xt2AjC|K+6}PwvIXyT$m*|^HA6iTGZ^W3a zxE5aQ+}c$qSpdrri8$h{Qpl%UL~*+YPaAy8vmpKuPez00*(Ti!LIZoCyK__kML@d0 z_DNugw;o1*T`7!=J~5wq$F@XupHs2KJt9bUez%YiIgd?&c1am)LS`lBpCXl|WL_8B zV`U9{IqFcfxhvvetGUK{%Lk(87Ct~O_ljWcpc{e6ihxZluJ!ur*Hpl$@ICx(eVav}ga(7%GcClu za&fdq>bxt*{sX%Nz{PY%$2xEty7m7j75yrTZBA=(H3^`INx=I$J}qk4D)h?1NH$Kt zL74=SRq+vbgib1SE&<}Q(E}oYXep;b!%{P!C@9sDBHHk9o>wx{mWjrghpPp3kEe_dt_FqL>zB#d zb(Bb+xqXG(+y^jwgH|e0j<#xSKaWw33RAa2qW#1 zNK}5-$)u7;-R|*Z;K5m`cMnAv9AnYFu-Jtf@wxJ)Nw zW{ptyjisD4?d9t_d8;516CJJpJqmBaYIqv?RX4*M{m%yt@{FJ4Z}%eAoLBj;P0WTv zNmQMa0hA)cy1=L|3S0x4jW1}2Bb8rNY~Y;u1>EQ4!VE0RTHXq)6gNfo3}j!_u3;>e z;Tf0z#YBG*y#c{(j59QbLE7~7iKqvV`h~!ADX8X;Wr)_TSJ+=wVxlS$ysK)ZLY`a!9kue7*bV$Iqb9yi5C?b zh#Ffa-BWG)@r}6%^uE*ex}+DcBQwP8YZBfu#dEJz*aHQY{5VNUKqz5^xNQ&h9hh#! zquy>{r{}NmIZo{lWxa~S}^cy_-4;g58UbJ+>zp1lBb|QwwCbJ7Kn|bmZ%1DB?v>~8XvoN3rN;wJBTJ`d$0{V;h zBDZBq$93G@%_c4L-O5Vi%S+z7$D1bcw{d>f{~LIw-hTpGZ(_9Rk~eBhc!Hk95q9J6 z1)|aRP3cL8Y_&DDsiN^5a5C9VF49aR8lT%R)N90K(uXSD-0MyFn@*;Ik6XtKTc)Dk zbP@Rl38m}4Ag4Z^lU`o&hDX|*p#DvJ7tb;GZUOH{0kgxR=(Bl8gQzJ%A(dZK2iCRQHuYSMD2VBcSxP5 zNxOVc9&-+`28w)RNYfr@g}Z@S#2jySdS_UM&67V`qTx8_s_NDtWnml*Us@y;hvO^c z<~Q3Q#>K^!?-!aeG^=;_{)$L7^Jl}Pdpa1i_M)oBaY#61qC}vIe%WM5AG7DS&qE06 zJsI*AoG{a>sp}4*&bF4o;aN=Q1=snutqaHlvZ4Tx2}bJ^KtJ?K50Fo-@ezIR%a_$= zLJDg^`Du%X?3xVEk1YQ`6AZ#mfO95jgvOeq`Htw|`Ibr1AbUQpC(d$daQCdFvv)v!YqY1({$0-GJ45<2Q4fhd1EG z@^6MhL>}sd9y#Fbh$7`ynThDLVvDz~%-e-lI2rV~Aj({V$Kt90We$NoPsNj%M@lI? zw-~o8c=)szQq)`0Wm?NUgYH9Vd(3no6HVK$Z=mU-A+H)qnWJSClv%;B8BeZu>(4ej zjOZUf+B#7g{nsEoKQ7-k3h{*jfZlPB99?Ug9Cr|zSH$}5+Op;d9)GAm17-$*@S38)~B-2Xd@D@^(vB37QB)xo{~%RZlO_%x?qcWioYmd6xMRu92P0 zi8W+L89LOpdk#sY(7!(+BHXwpOZ&z9f`;134~y#)yptd@22f)Lw2sYJ(|svz+msg% z+-frd5I7_fdzzsIu70Ew)-BCihFmvtC&^$F)gHpyb;k7(CxnST{OlZyx-nlnx?YG3 zuxE8tszaUh(EB3Tr;GPjedB@az+d=llR^4qdmjvL5bGbhMF`*ggaE4$f?pw%@#k6h z3hDLXY%E6RLIz^vJ~)USK3GkwP(s@Yn~6NACs7L*UW+zf%8?z-}q0=6~CZ@58DiCb7|R_O`bZ zy`N`l`&m9(YbpcRYD3ggXY);sNW$HpWn7X#Y>7r=DW0lImCqjsLd+FcpQ^s76cPK> ztH@4%TV8?A=3(00%sp-G6gCh?Y}5qq)iSzM)+^R=i-}q8@HP+g#r}Af*|D40Xcf44 zB9?BiN0i?@8h=`OenqDZV%FNtR$}ZX-uT+{fldQ*#ek3PZcnI%wICC7`}DQg?jma z5GZG|DU%4qzHdcqk2gCC))czFrTB=+9$>@Qc*_V9G0%uWAK8EDrmW?E`MGaG)*%zY zz@j{H5W-bqYJsTHh{(YF02tNf=JqxO4O<8R-pf;M=#NYFL}^55>w)Nysg!72-u)}R zGA~4>Vt!8{K#1bKWa6!&ZCr3`-Zyu%121E2srdtWk++N5^`Esy;}j zRVXs4SM~cjST?Nc1H~1Nvz+^gA_IbJxbB=}TAvyNkmBP=OlTo9SXLOcz&+{~flJbO z^y_I5Wi?EC){}$jX1S(XPy*r(V3b`axff zLY&3~ORJwGAnLR1K9P_fX?VW8FS}Spjxu4C*KYc%0-gDc2Yd)!SOgQ7;AFu6m+jc;_UG`a zDNR!9<-2(pBRQWM^n3d_y@E<1;%nCuu*r#AqlOe--GcJ&K!TU_S2JbFKgrgQM&(nU zrnJoJ@KD|c{Of~gMg%(I^24Vx0x9!sGW0D5|3zBYimL7G#|V-&s;(bF?9=ytRY_|+ zWWY=I$xIzXPfDI)L_9D-hc6c@R~j0dfIq4uS*G>%)*mr&-R;PwkR-2S5YYV3ik1#= z4|8p_s~wy=hHL8{Im999)m7IBKh@ti8`sl(G&han=}meMJhv<Rs#>0S`|7Y;>PjD>t~Ph zoKHZ)^(rfI1cs~iLe#kfuJVm1?Fu>Af~owH#Lc8Tsq*@)8~Pgde=N7vXOhkg1{`?e zM`qaqG4IwzP3O^Vuy;$E7C|ohEyi)QT5#OvgN&olI9{`FpxM7yD6huE2+?;?u76cV z@p*)vJM+66wtT;K&kGEp>0ZiYr4QSl&hcCwQgY?Q3Ri5H&{8ncnN}m)K`PM4pC7V7UWD9T%za3H zfiBn*>2{6*CU$c1UO4n!AFoZXp5FnEue4avpm9XHxxt0jBOJklxQ*K` zw(yw_vh38UqAJBOugg{pVY-zMjVUPs=8n2ob}#b`ld^LM#vgiMJ}&K`bWCvcfZC&k zbycg2+g9%5P+5}F5{;B(OW^m<`P)xBa=z9`v2j!H*qd7S`rnR$)uLlJZt|PhNEyu5>iK@+22v&azwy zmV+7}M)s2%<=YE`^-fIDa73=(IRnO7#6^42(TW0tJP5%rUwdN;#787dfZyyNKsk9; z__~X!E!vq>k;KU@ideVeBfm=QLs7ZurkX=e$Y>-hNXtn%_uCIej!-u^&r^OX9Z* zndMaU;wi_s(UlB9^;>8PTG&`Sp^Sa%$aKaeZ=Bl4U_Arydnn^^)hD z+BBCU>r`#8G7v=la)3`*xvb`CDL!rT_*Dm>muN867LT(Pt$&i*hu*2OZDN|6(3SNZ zAxWJ!^XUv5x!;|S@Cg##N}_<772zR8$mn$3d&x7LnwL94_9_@(95TtY_PzMg9-Je< z%}wVhl*fAk7FZo<1`>iYI7fYQos@n>5luCTyS#p-@Kh~_Opr#gD2r|o(tXu!lriv{ ztDZBwF=hhqQ!6zwXnZ%B+vKl!OO;?h>cW6d+F@xb3i#rmsL2QuM)Q$422)CSPFr>O z-$tI&KmzZtI-vTfVeE={b8v&*L%ot0oP;05MbQw_z#xP;=M+NZr3zI6h{d)bo*yYw z`fxiOOTBUOX2)KF>=f?VNwFO#eRr|RUxIj1?lf|{1uV+|_i8y(LHmwRZDnUAuVfqm zNy)=_JRnHJJC281Q+uAFL%?PaCHC^28~t=2)CK9eN>6SJ2RY_KPz7HJJBKf#IOUut z94X}eGJdK3pITzeT6e0}KS!U!ds1Y%^m6>CQGs%`h1CjAqo~IhB||)aFZ5O%Cz8-# z!LTGCcCa6!D9jlpX(~B)3GLKXe0qGrVfk&wCb2m_D!|9+d5Gq_S3ApNB^{O53voVO zZU8ig?BLt<6a8o@*cThQ^ljcY+|nZUIezL5E3lj2J|14@5u?6U1Fl8>CttSj3Lan} zY^GnUax)K=n0se-q^GsidnhZ>3VXA9yaL&c=HoPRs}rQg@*M*Cy2ntA7i=oFv~uRi zTKGsso$xU7CeVz;Cs#Ef)mh~s6yRzN2%Keh25MAwnm@I&n&7l2Qqg(R^AdA3Zc%y0&4S#*ViCGe5jbqE>%~iRar1gd>Ox{IxzN;DyoZW}Uo7&75Ok#d%Ka zt(gw{POcX;FL>ed3k9io0M+z1NXh(2QL8+1V5cxU&r?4FYzvbv-dYaGHV_5Qg>;@G zhqhuEAE=#e1C*Wj6lABFpngQj)C+; z$nSSL%zcKQ-Z)IR2kIYT*7##aac$G6HZqj(Ptehjy zxvkBLo2^;ZTz9zX02}{jf(_VDc-%JWTgu$&IzsFX_rHN_D2E`-1&im>6gl8AXj4_P z>R)1l%8Ls!{?TtKgyUVYXvc?pz*CBIpTK(Wj;N^;NT9}oA)owNMx|S1(mELv&l>b7 zF}2Np(PsZ*Og0>{9;vC`H;|g=dbwtv1h31hzS^S*3*B;`L4$60{>*R zYsef!8lwr)p>y4Dl8OdLVPEe0HD+NIhiBN6=KD74WSvV@FC3#2K zqVKs+5}J-8!5Wq=B~6VNGI)`moiHezlf{CGh4H%`gd2${=_PN=pA{>EG={J~a9oFg zSE#nXM(|(??$7b|nxM1yBp=csk{d3bp?=~?5rEBY0!L`DSUPXE9XdUN| z4~mN~lW5cZ;kqnQ|EKiJy};wx%pb%eGi(P0sF?!2m!GN&03OjOk?*D|!&J9wac*|n z9cCs3YW<;Fds7Avnf5C&&_@d#3-l%IUxRGu`2nBdJJPU*R=4xVxt2EoTMpG)gC+{% z?KM>3TZ^|ZAf=ACh@M7DLwh5oS5&oovX7mvZ?M)phaZ+rP9X?m8CxKYQ|s{KLZjbL zQ%sOzhl{!VYzd2^N^pi@PP^n~V(3$i!rQveP=;H&ZBAmn-<*^$`zD7dRXf~bIeBfb;BnK6 z=_&u%$bL7~LY}03cmuiQ!H-6S_Rb4OCqa^KV-ltW zTz!+9Z*|?Gk9|XGB?`Q0wuVw`@qA~U!Z~olg%>ro1c3SK37qP`IX2bR{sa+MV{e`e z7?m>7`#h`I!S=5kob8LgI&X$T5-z_$J?Yu+wS2-33&9%0{o@MCb&Svi!1q8>j3B9~ z65P8~YC1L_q(!6rK`t!vo`N;1CVBWVAD)K@10omhCtVd?u^gFVa^Y zNm0!KV#8ooJ!KZG1ZA}-ZwSh-fgnp0?Y!wEPlv>6<5dd8qfp?>x9GmsDW)( z&xDxLM6S89B?S5%k3P^RJ6;si?fWxmzE4FR(4x~;?encWvB_y4>HayU_uihaX+I%8 zXh^Ek%m4vwCW;F870CR!;bSgBwU2(#%VdZXKO+1>EJjlI6OhNy38d@)$vYC6&B+ij z53UHi5&7X2Oms_a6y&8=H_qLijb%n&7EvG{@Qp5_s2yMKyF6h(tV-%)_n2)r(9Lu< zXa);03A>tNCNU6@hWWd9DBFpB-jcxGzK|t3xGq+whe0v?&0d@09omWeIxm@#B)%(T zZk?v8Fx=AKixk_JXX4n4O@cyc{7-7`YrmSluo$qO=nPkMP1u9$?qq|q*V^=!j6n$y z(XY)M72aw2RQ9Du&d=FgKmFCw*NWC?o-^1S)~I5QbhQWn24tM|FyT;LN;(HoGKq(+ zJjAZ)`4_EScn&9XMnp40UCfoz)EGf1wP&GbFpO~tdQPP6%~>t^6bG(+F0c+thv#mi zDf+7#l(gC2aOY3C+y8Y7k}Z$57uUlTO*KcdSO=%g1Fn3zes75_wBD1!4mD&(4HA{5 zZ)0I~JB0>%OGk{aI^dW6b=*~hF-d3x#(4Pu^p4l};H0NsZXyHVSJKM|ncmqS2uILf z^ZarJ6mUVdFBfLmd-SSfBm;kJjZIe-LE(ZikBfgOqk8@Ph3o4NT2(~tsO6PZ_%9BO7d^*ZrLdo;kOJ*URfFT z6*-S<+2w_=U#~12%9l%-Hj*zBH#V3D&%?wRpkk)j_MGljj<+iqhw2jT5hhA9?YR{s zD17|W-3_gw?{nMT!PC2_ur-AyzLfzwm|b8Cc)AU1h&P>ZBpI(}8G|idGv=|cggR(a z>MYH=fD<(RKF)y2bbg#T7>-HrZ+l{~CgwNp*%jX!CX;6~_(>xEV`$mEjHL7+jGF3L zI|nyI*G`eSu%&Sf`Y`BValQ05(b;mCK|x3!t77T#Z4C7T&ughjtL zd|GN)N($|21Zzj8x8I?Y0N7 z3eVQcDd0_DXy!XcEgexekT&F<9_Y;av^VsrJ7-)XBC^K^%C}&G=h7upc5nC54O&$* z-})>H7BT6D`zmTNj#k(oX5q$Kgf?vQcm2)4i-ieq2dakA`mKxR=Q-vsY_XtBlge3r zUqmL#|DB50uxM+tp-p=qu%o&v{^PuId{x|8s^5-*Q^M9~D8<#p9ZD6Yo_tF)gDcV; z%N)qboU8)I`!nCx-1HU=hp|`2g93QGUW+0>O!@Z2Zi0xYWnAN4y_~Z z!@Z>Y+_D-HXov_W%&Hg)?`c{qHhYAVqS#|Qf`?O}4Hv%7V)rAX>i|<(wgdS)T?lqj_bBxSy~Vnj#e zGcyi!NJ%4~;crHCOpnUw8El+O*)ZQKy-v4AER;vWT$mod9003CX8 zLI-<0zY6ow)lS-d0w&T9wKQ3uTOJ1T-?jdXNj5$>sk2(Fnv8{rn)8pl6jnD-eyUb> zUeK!_7h9$#?EuhxBT?ojC)RBisN^WETkJ84)ND;Ekzd+DxiXY2SeXOz5j+&NP|$$J z$@Md%I}Q()4xS{*K-81n*`pv~V6e=sKRO15nFGlvSXhXvo5{%nI?z;Jxv5QJzHDx0 zb>>jk#h?WL-;(SpU0u4NC2r`PJK#l%Ni<;(U~=>RcRMbP!TFslcAZ@X!&k-GvVJ$|BFHSoI5h{?JjELdR{KmP} zBe1-oioxgyS$~YZra%QH{!p-09vrEA(soW6tG;J&+crkmx!hloV;bqG2HS%dYe-UG zuLV*j&qsG9zaj6SLRdu4?>DlhESdmYzoFiz2Zdbndabb79ic}^^T!(V%{72M-tzC3 z+r-JPxujtYZ}%*(>yl^=e|02bdx%s~jW|-1yyH*3btTMr*0}M@(awTn=&#!8@%CqU za1zw5YQ0Z%5|F^s7OfI^z339u7s?M`6vG3DX2e=>uonbC4&Y&UDHdC0eW&P@~69F}b#tviNcwetZ@%$1G9=3=F@qJV{ zr~L%Lus{&*qY!l=0zG(8@hd{wEMH;nDQ{k~RO_75YzLKtk6q)pg1p8xjHBlA1j!hz zq2+IP!N#2C!4%z57S7~mhbXCDgA|E$QeyLrsU7W*YF9a4CA?GYCr=yP(>Cn(lG%`# zNpF(W7>R2Qu8h7V{KmYGBi)J}6JfEl$-h|jeRejETje%mLJ7kH7@>z2fi4*<(`4ln zK}2ZNDT*=@ZI z1?>k#FBpz5^o7U05G>MwNE0E#zO0LeU)6?AR$0IkFpjp_q||&z3w+Kr*n(?=OJ)x% zg9}R8D4vKNR;*pjxI5Ewk4APPC`63XFpS>GGGYev*Q&V#`y3>?R3w+olR^XzO5Sf? z&-b;|4+IRHx;=q|Ken1l119l+{V;HHrdeeVPyF_j?L@%_0p>pK1!*&>kn=8v9WxvY z4x4sojl%Ph29|y$?-4`)uOnyLz6Jp*RkkARA=}<8yIzOmd0>vg)dk8{k22SfDezr! z@92i%au#|sPx_Hl@rnk16+;@>5e^(%hIxWUSsp5g2>^LT8fv{d2z=KrFs$>y_RdeU z-cH>n{}f2%tvGAA^t^WaEI#Rjj(Vk#B(|sMF$Q8!KY~b958DB%M*Y5UW~ZBX990K4 z^O(YBi$;u!ijA{z;i_NEL^bwti*cvao`P604Tz)|RBYC4<%v_G1}4;mkvV(>2(swj zL`Fy62#(!u*TYCuz>s{_!o;hbJisAA?%Fpk+JQLDT@i})!taTb>eD9ypFSsi(E`1` z(C}W#z&2zo0)HVMJfL5^uQ>}2u?yzqfN-=z=#3sShrvX7gS}(?<(elqr zOup<>LrYh20{t5#3Zf3~*)9Lj5A!e-|2k~GPxc;vjH<9)!_Ww$21ME}7^4fGH;{d2 zg^Y1s0$DHZw=+3_zA61YI#=Euj$aBvLt-(X>{}o+B3$c{bX4$~jGQIjQ(F8dWZf5# zio_iX2Wr1I`jg13NeKXkg~T}YkNxRP`OYq1QD0o6o4Wcz|3V+~m|kkt$RZwJZhvYK zq^yo6yYPoP8I%aL7p5%A1h92!TA% z3r_PP$m;T5?~^XF&UbEZsa_%mw^j3v zv)&4yGT;(rk7^g&ek4?T$5r?#4Tel8eC~s@`Uq^s%cV7*i<;{pwhgK zM<$9;OC>;JlW$>h&?CT~wQ(bLL1aQh0HputM=hsa;+TfdB@cTZ=!SNoz)*YoQyFAS zK!$wxHT*t660>7kh|s64Sc}08RmBkNwQ^n|LQTvGxXP8PucHiZA+A4mRq4py{sEQ! zGJ^8t!E(*ZYxlwz>+C@mk{qA^?;}dWEARL=l~)uy61MzY{IFuZ^$xBf>Wt9UiuIYn z2rg85+zQ%#)-Q%}*ej@1P7~_|fsO$ybQ!8SX1HM7HXy4|UGhG6ra_MhMmvbQ^c zgsRN~v$5&|6KRmwY)RQk)LaIQ_U$!FgqfNjQ5`vO8EJAyp0oZ|kVHW8KvD)(ak-+c zl^VSA_l9=WUz=Fe+Ohf&2iOY`rM)Slhi2b$%#TDC=Fn>kOi_;X4MXyVApaM}hVE_q zy$`z`NJr~O=p}iZqk{If;3XjzQhva>9H!#rVRkHst- zEu@>Ry7soKca!=?ZvDwg;2I}Gn{b7K1sGe(^Mmg6TTTlU+bejCjg{~M^kzd}I=ZB< zZTDS)0O{;tzGCkRFpW*RzLUx*+yXfW{sy48Y<})VsSe%ZE%V0KzR@6IG{O09?vVzK z-NA~@-ZBto}N7bspK?X65F(EM&7{pkAY^0X~|P1)H< z1eW9b^3qdw8f80Wp>OkL08Fn}XnIAcyHz1O^z!f93r8m?4HkWCah@~?jpyFO)X{vk z>XlRDV^>rDU_3Z5d|Cwiz2?m33b&}CY4CN#ChQ=U7Ap3DNddij9c8+q0t#8XK}OVB z1uUT~zc3w90^xCp^oH05{pmef84PnN_Gn$6rc*`MV!L%2ba4 z3JIHy5nz7WIY%)xdqT8QGLx(Y9eot3EK2~?afWZ((7*ATbye*H`E++&5~REalYIJP zQ>r$1he&n8qrI5NTujR2+NPjpR zJ3E0OIA35IdrAJ_V0H9F-hWX!w&NIMTP7JUWBgAYc zZA=QfH-a$6z(b~6;uuPt@07?a&CYz#m+FDUJU?k+u?dJ$O8sd;=!AJ&KpZTHR3_Xf z@Uw-Jv$PFxqi7M`q^VyyHOn>;SPSSlK!11q=yayl+mMXJG?pMUSxny^)`;tNSct?X*8dg5BKRMsL1xV6JZK zc>6N(Opl%<*j3fpi*=oBNZM_f4b9i)svI3sQ$<7RBvRY5q~V>fOR@!sj4Cdv)Bh`CC~*dEA%N)MJk4=)8?P+H#JM#;{fGL;)6$S6j*;H+q%i z>xJWt)<*+TQZn5MF|FeS6oHd9yk`W&XD%kAm4E^WMuN4}p_g8TQ(szE#2i&50vD4~p9G<}2v)Q~Gqs zRT;W2c#)4zV*D%+KBhpit;R+CEdyPnB{g>Hd?C@Ac?GEtHyWO!`K0umLyhR%58d?t03E;DOQR}Z4UgFd7ne^aX;sd{XFP{s0VYHP|3$+(BI z7ugnW>)Iov95d2J?@bL5Xlq&OP%{7CiSqn{Uhy$2X2CLbK`e)>$l>m zip>wMlv1!WZ6s7ow|7~964aDz8(u4VUrzBevYc`Yz!;6ym;M_FH31xy)l3Z! z%33*?a}y|RRs(E+@UCTkiHqyhldJ)NtUtlkSNl?Y&~vEZ^^$F>_DE39J<=^gW$#4kFB z@+CwbMT7@CFf`<=yiJjjwoQdf*ic-AA3(uN$gOg_p_Bj1F|w7<-6Xi$3!{+^ zYkm%}$7g`ZN;06XtD<)Kz+7;b&lVDj}fZ>YH zj}Tl=8F(rRO2}O=gB2%k6w_8Y)2+RPf@nb0l2_xB^3mu4o*&v?!u;C8^g>puaS%n* zyb0Y2d|iWAy-(l)Mr2}$Aq7yP`BU~(`WLugh>k5p-B>*DI3U-aq_d5m4r+pNNr=mw zJ&U-*9Wy|*d_LRfxKwuaZST@SPD-A9zm<>T#a49os4MKiG*f+c`C7R;9_ zgFTFYtp;+SbI`2watkU!Yz%l-Np_cEi)|hbm$nfVUV_$R84KmW=G;@Gxs6{Q&17Rk zryqBEg!X&maH|nE8-;c{p52y;GH`=Z`~R;k%&aYz1hs$-ww2K`8l+wcWm|Y1ZJLFErd6~ti04`r5$1DFDm<)at2O9Y~FJssP z^TNU*4c4*IeD(ZpYIIIsZeQoGuavlc;SnGC{Y97SxrA2Wloy;rGFsx5;&pNN;YYhH zDH~{6MXk;6>S2N`90C#ry>k;Xr{}_wprt@8qH^9`M9eT-`@N8$^nbJxI{3Eu43UI- z_3eu86aDN%!3dt&CNRKT*_YU`ZYM*tD!}~R{*1gi3kakJ4#ccb;kkpRMym)q5uu=Z zs7OARxIT?^;+Y91z__j?KlZWmNMLff55ghS1y%Nn6p1RHps`qr2ZWhoo<7bL|NZeO{Q|)mti|_6;`p%lB6` z9Ka@*6m+{uh6$UU$_5()rl5lHG0oj#uabCgQ;)B8_>>kUL8Qwi+P$+hXtD^9f2dQn z#Z(&Py|^(=Z<3%EupAz{Z6`KO!)(GO|6;ga<=;vkN2l>hIPY4R!^* z0-qv`1V^~H3l5^6vd|e%Lu_bwJhC|uN&K^)!X_RlXgyR0Xp3=;DeS|7kn{>r(Ox~* zltaRAxE6?pZ&M42!_E^#&4fz3U&gHR%dOx~GZNl<5e1$0smynINw~7&FF?PVHjGxy z>iMJdi>X1%K3^pz>eAi6r)%>Y7&xx~5K4*8+_HZqdAD&%f*$#)xFf7&_wL8S)P)yq z0~PeR;nAP8(h*PN#fF~;qEnm_TGE?0z5o0r29$pXL%YASqPk+qxTy^3iB_hVQ}1TJ zmgMn615dmtPZdfjyn~-n_tO-(*zQulsS;XYQa&^B*Lo<_>7n}AWleqaF?L>4asbAM zRMXh+?dY16|KItd3%wor645 z$&J?p%T!D#YP_oI1^N5<2+XwzseZnnDBZ%Jj1#C;@TBvjSg?eTDW@E^tHMcKx*te+ zAoNlT4e{D7FBk!Qs3hn0LJ;u}7geZX9wO`{bwTP%%j~!DZ zFtBlIYh}%2+x`8PxMKH$v(Hv+nNc!_!JttJ-Ix+B6$&BAJZj74Nrb8hrUIIb$fcFq%J_DjX1rQ zCJSa}lO}R3y~?VRP)sG&hdwwLn5=W&8)3+2c<|$jjx47Xo6c(I{4>+nU+h_bHF+r5 zuoc4Dobfv!vbx3H(ZWy?yaMJ$SU^nWEXaPmzB?}ZVR*gu5T7qAEkoC7z5kKTw%m<5 zXzDSJ?AB5*h+PzwA508FFW>j+%{I-UhBP0*4pNHpG&tBWX^f#077trTorQ=30=w># zY4_`H6-YZ0;3?duLkC-UoN@RXsU=sA&5C%iS%Igz#bHDrdbRvJ()fFKX#Z|P{;X%( z6T4kO9qjuLEx)y}>sTDvnr+vXs8)9uxDdXF7v%rm%g6u1Z)j=p-IMy?RPo`lUW|EL z8W+B(8L~%40{D_WB1oiI#3)7NIp#OfHTB*h1r43G&#`a?L*SC$?1`tz7Igf_|pNddO{W!0B?YHiP zZ%uh#F7z+VGlL44NO-YMGB?`T!$NfNP*IO~HnElzMpNRTpDWEGVPHVYzn(!*Z|)C(qWDX|n+; z$BoRDfd5>6Qet=)>+@_j*0gnTo7q76_I?JKi+~Gl6>WySWF1SR=+w(hSqm1?Go~36 zF76=eRxK3rtB3nQ&dY{=X&~U9%eQUR&3H0Dj0CzN=JaBSX8EFOxG@7l3+y!O!*Zf4 zhm@v%;uSHGIBuBp#zbEWB-?hqcMX+eX|~aD3bj3bGHmMDAaf@u`lV z#H)H{HLP;q4NcEC(R~N)GtJt6kO_D$AZHuRkx;1D1m1l(PeWa*WG@+P3G11+Ct&22-r3KD0JRPkl#SjItZ!*faXo$^mxj^d27FMD-#pmA5U1eHUK)iYk^5l6i z?n%#n&h(r7`jfCvbsK`wd!rjk7gYLl{MqpU{YN;{-ks^3#e6Kca>u9*{o=RG zv4qnM5SnueI0;G!`{_Tnwum4J%r!#>=WSB@*5#;a1JVLT6#)C_Q9!bN_cg67%O1HqIClrPg^=PO!*e-TH(?=h7K)AsY_ zhF4c;^q4G!0ZF_q1xDFAX2XmTzDmMcw^N?c(29L4+#+`v9UK~s$~*#^419sKFBYqm zfcGL6g4eC7rWxBPH9Xr!>#Mnp=(z-05A-oX2pXqc^^ai$KC+94Gz#*5?q3KvkL+2 z5Qg_k;dre6PH52gjdwW}*)-Gs4dLoi;2>=NdWTk%)n%}v8NZdUw-s9rh9dHu$ZXE; z{je{~-HlUjP16%l)ITKzaTDHF)i*LnHY!N2WloLLkXHu^P|GU+3spC`OCX8WyOlVJ z+;;(#>JEH%ZLC* z4vCPO-o74%$XkH!w^5SL@FW0Lu*w%<3=-w%JSDG!476%Y&qSuHwyJH-_`)=VehiG- zdcE#Rhk9V>*mR5!Xf{>;S;Fyz^_WK9K}F*A+kY$;=rIq!uXdM0yfd_5@#a~m@{pE& z{xzK6z z{r@9L>TZr^QJZ{JPe^HG2*Af5EC5MBw!d}O`=-+nxBv&Wbytyfr*F0xW;XB+P5q;a z?#|J0$=CLga>Z|$R|PWOmQhP~UKFQ6mD3U|z4{mqvbSGt`Qp&7s3Bd827+#(ag5zm zuRC6HSHhIVvL<>6T4oj8ckaunS1>Qu$R(V5i2%>NC<}82vRr(q+=OSlOVi^gq`|=nd(i?0NX%{C zV4OGXd~!dQ|I_871gR6MIlYLM%&&E)kHXk^@xVSKz8oq7a1p#qw{zVCUI2>r0J2%p4Cktk;blQUw`_4DsVtE)?s$oy^1M3nkuQuJ^a?E8 z>Zo&l8{><~y+|j0<=7Fp%qjmNB0!wFd8kSz9V;r(=aM&S{|9Y2Le+kKQsz?V(a!&J ze6Xb2B|phO#ican#40TAsHw~`GyET!PW0GEF3ctsD)}7h=S^@>kpfgPZRit}O^)@d zfmhqFv#ABy$TIm@&I{CWcsk0;0fDnuA`5T~V8{Y+99IvH8q_`MEGh&W`b>@pq}?ZW zx|1#t+pw>ZRr)(de~%Y}F}ycO+Zrnt_qeEpokFtiw8e84mvJ>PqR)k>wCokYRhhDO z*{^@e+|@O(|7OvY)ylCyao+FatHpXPC;Q98)!2^1l?!*;v(V{iH$rC=(e@EeN-4c* z#&I8Rfj-GNM6o-=KRCGH2&w$E< z@N643+#d+!@q=ZbiqhwU3{5Z&#ahHSkhs?ZXhW&G%Al!}z~!BL(%`h`63s0diqBvb zvTbQMo4P!aD$@tv+r4p9Ad8@bb~*+UP>n>t<;4nRWr@~gg~yY#=GH0%!rj+MZl#I{ z;O_m#Wc6HBWYQ)l+{%5GCLMf%9n@s%_>0xps9{*X$h(3LZ)}YDr#GSFI6-lkk*sDNhz`9u?A;F)3>gN3w)0C*4_F6d zrwIElj~{jVd#4Rb1DsV={3?y(=_zI96qiR7Q8vT_*n;oeb&ZH#Yu^7A%BG3C8! zue{&JzBe7rf*Y04zbTU~Vo%~tLK)%6Cm!D1f*>QRDxfAMUvj*`*zs(Sh>4jxv7qP& zEY?=l(_zYsqFjpG2Dw@*yZK0C&=dPtk5atjj;bF@qG*$&tM3ol8g#y`PGtSaUzy$* zX}Y!LV&_mj9MB&QQ?~-lULfGV`alAghEJLhF!D?kb(3Fmn;ATzmooIZfWCjb-9rH& zi0)Fr8Zxy{PUd;#YzOa9#ye8Ht~>qLE(nG4SIh)tbahx?*07+iDu|X~Xq);YaNL>b z!rZ^0)^V&+wKYfK^-9o5Wj(4tFYHu^OU;S{bT@31z=f$pwWt3}*ZI4+e{4MLCD8*? z%-m7KmMxhD2&o-CLScn#v;{b)WJ7ZWL?*>X05EiIMN}W4iO?{drrAlZZq_!36NfN{ ze4r+esuwC}Cl5@w!`5~Nwh+l7YCxtoU)*%$KuYxeN_{6`^5EL!quH38Ac;!^IX6nU z=={!|TpjIr8x6r0pJPG<%=f%*@2b~sBq3c3!-5{A&wbX9zk6qYDPy_Co+|pay=18V zS{f4qn+L2;TQIpWDF*=g+5LYu(8ZXm2NyBL+b7c&k>{aS?vlR@lU1cWTp*++`$wmI zFxC8k!k{QT&Rk{Aqr}e5ebC5s^D6WWJY8s&Wr|k3mr2KydV|11D)HTWd=cs;2$D$I zR`|FpRBLnWEWgsN)2MhCH)#UswXq0AW$pUzZ@`;D)!_1IeyWZl3ny!coduILm2JJ+ znVl)Uve3heS*u!wR@)HRY`hxP|E0FFPKh!wA`f1dz2dT@PSjIEnHdqqsUjs;C#dg| zRW^!DYgYyeR}z$D`BT6R(ANT(KT`*z@b}>5;W_dK9?5ps28vtI)P6VL>2Fg7Z6*^sE0@FJ zc;5U~C*o%{ZPBSmptX=PLSZow1wN5W%Hk?S?pD7)u74|-wIAvPglWM0WLRWQVu z&hg!NQXSxShv8=0DvK^OJKnvE7y@cc#E_#U^7E%*s) z%4Nf8HF5H$_h`-1n4P$7z??vHywdV200xV&7)UntDYc(r&q4Es$t3*sj#Fd2~plIX?As_Jd=7taR7 z)GFno8i_yGV<{d0je+X04$bqcF;+0N0@~@}Y<9-91XfBS*lFmKkqghu!2WPDo`%0_ z33OK`q~V}B|pgqJB{Na7A}u~b?HHf*`#rLpZ)uW4StW^9=Pajwp&Z%p zMgr!zUs2u(!~z^Yt+>X8WQ!~?FFzEd)`9j`cv-*^6#;A-u)$K3Aj7E+EVa-2FQbl5 ztpko92g->P^Ckdt#|Kb;{vN|##-h%N^T4F(4xp|?9sC1GUkLAS0@J@UH-Z!7NKhg% zAt##3*-x~aQB%As4>LO<-^RyS3wz0>+r4twU^53= zE6V1tlD4|X@FzVgZCvk*B9|lZ;={tW_bAoiS6u4rvD4RGN?8QK9Bar~nQ*{}+1Ajg zN=x%f({@9lEkgp-UVD@St3v9__I}fL!yGP zK|H7lYL<-I?Apg}@}U|x+q`iJGZM!FmZ-p)hV5=URnlV70K_jgRkrZrGMGI~69A4j zq%1E@vkm){v%67kAPJ6YMVl=!j#+>nh1vpvwVvj-H4Af4>{j4Ze zwihu9|D~r3m40&tKo}zr^<~o$VdHRY=mHkhd8GcBI7=OPgdpHMxG?O$4424nwTE)B z=7r5wG(c_ksTK6~rTiSY@DP%b9i>w6-kjnr1R=U-Z(?BMDZ~O7cTakez_tx5@&!Dq zQvUT#G03dlhg>wag`D+xP6Boy#CQFzn?j|W#a;M7thbq_M}0j{)!#INL9W~CmTvm{ z)d1Q!bduSZ>c%x8l~sQ;2KKZArX_}e z`!6E@TLV#mpVGY5S|I<;d4l)VmeK7nRqa=AoGk!U995eh%n=x$+`0%rqTM?V$2YAy zzry909paDKlF{2QsK7$MubAZ~WoE|=6ro7@zY^+31+sB=5aoss$41IoB34N6>mI1o z28)sna|CxgLElnF=VkIe(!ozZ&mrU0%wS2>)>L>02qf5zw#*` zqHsZ`V7>}h`13={CNzuP&+n3Y^uoO#t}u+-vvy%C*mg3wDU-b{HKvYLB#vPyBkG}b zb-k{>cFZ6+EVVok&m{WQUTb;k&sTJz<{%xovVD_Gua)ZBQxPoCMCgv{BHQI(0R0UL zKB(!rHQ|7#fPwf!8v+emGI)fE6C=!){>~%#r_2;r0z$gAIx3e2sEnH;8Cu$4?jmMS zj%2NZ=iO;awbS^)6e@etBW~Y;ymYmIvCd!Jnt-_K`$C9#GuSUTm*)@$7UQRT5PsGK zVBLA|uR;z_c(5LMsmFYIwhcqT+J0Te|DBz~mz`3;no!@TR2>c%6fzkLEX7m0eRQA} z^!{{2$RT$HU2$g9(Dd3)J5zJdL`!?X>KQ~_-1~>U^Z(c4#$X*|hfwBHeobQxSDm+V zaMWE$bTCbnzTocCil3$o2c@Z=L>a2%sl24J%cZXE^ScruSqs>uFwbjmZH~n)I+o+J zGeANRlRB5JB6m=jp0ryZf@!1#kZ2nUiqn86H>>D%RDnLla&g2XE2>{E8y7w7d+^BO zrBWt-F`-Nx4QMk`y`Yb%YQsSZRLN@rKKVbpb0ULE* z#I=4T`RaBTXmML6E@d9}7wndkD5q`S_-gLE_rZNF<;$iNbHhuMAgYLNUgTP!ZN!>QmLI^p4;z=m~prv6( z$7A*M=Qr6Vy42}8?j}6~TZ&oOP)DXdv@T&(I2x|cSJ~AnTK|C*PmWu_Z})Y=8t|VW zoTWju@o&s(XE`+XUPEHHo^)(ttz~Pn5H1=}FMO&=0TTk3eZ?G>rYT#3h}Yqu6Vdq}=oIn(THo(3xr~AZp%h^SVLp zBdiW^H9u+-J@pKrA@+JMwGk~PL|=Y=`xw^8@Dsp==bTYc1D2nGvp?Ab@)McXY_Y&~ zr^A{oArK8WoW#EdT?iT{F+XjO=g2xAk(JO|J?05z ztB3Ka^3U=!mQ{(?xOjZre|vjc_FJ6s$j&(i;fQ)`As5N!F5iPB^z{uYB11%WkVV59 zDJ-NxK&8=3bUyle7`ynbjmN2dhT@&)X#t7t#zI03O+ik1R*1}H2?{gVzO9!6+Q`%5 zv*~k?T@WH&W)Z#+%$qz*6XR9HWAM&jdf$iUKT+kq5WB}jq3v7#x-f%Ti&6*#lC1iKcF z9#MbFf5^*6A))Ag!=l!0-A29{l0xjP=Jiz?8~;R-w9*G^7`?e%DEpELi#UjC! z`aJs8hraFlsz461@2Jt98+>t;7}@U;eE+R|92=hf0)%57<%s65HwguAKg;5vL8iNH_A%WE> zp5zya9g%7208bR@ul&cUxM=vO{Fol)807|S)!Cl@TqUb#u`)4&0L0S1 z@I@a1)F*ofSsBIEKRNbI_Mhi$@=>KR!sk;Y3`^}Ql(*O@#=OiRT!v7+EC}cK`AfZC*ZK$}?%(gjPZ+6_NYBidb5rC2UDj-s$ zzUpndFl{mUf=yZYCBa;ZKwf-U(4}||aa4Gfbeb4fu0rV=2MF>>g5XB_) zQS5eY@M!lHU`GV(UH?TSREH{MgBo#5fuMG%&VNU(k-EG{o(Cu0Z#sV}{Eu)999-^m zC2|_)Zl&AtA^0{DFt~dpEGYFLnAjbf2KM_&@8&!zz4H2a5q-0 zeN`;b_BdEx3jnBV{%E`t$JA~tf_ykEWJa&gMhKxz&bc2cC$Qn9={e3B$~m+EQfS4X zt(!l!y#Z04U%I)pOtomegK8zJH~7SKug(rU7BWy%6Pb9bbrv$7JA;0+JeAv4`*jdv z85|4K{rEm4*S~q{Zaz>o{nV3tRVAD7_-#12(lnuY@kc$H(EgvglDhD687(-%oAj-^=Q z_@T`8l_z=f#F@eSC3c-GRuR%D=*ww$Q1H!UkkH1bMDFfAb>A9(UtvcC9uvA=W`qnH znv|uq=X$w{#xc2uJ4K-*$S*{>@zX~GlMf}K+H4JOt6uQR8!z_QyGP6Q1ZTh8 zcQfR|cz#TT^Ws-o@(+bxj024z=tg{4Z(bp@FDq0BK~f`*3DwgCxh#c_dO3HU;TG_F*~2ew8IYSEKr2{WYa%~+W&dSqZ1=H`L&Z4Wov zEimtQjr)im74eXwouJ2TZt!$NuIi_E|G0w773>2sGyJjH+Eg;x4oaZpwJw3K7<*T- z1)Oow8-V;QJMVbeGnj$4CP!epQVY#7!>#fUonEC-f-GJXjUMCrE{4H@S%!_w4Xylv z%W^R*)uJ7NpE2uaUBgkU`qxIB^<%4Nz=)4+Li_uecObb_zi=|e3x@)#&5s92$RUX) zG@ahca*9LEt3vDr9t0Usr4MM)x8E@_>6PziuX?*v8)A10)=U$!=oPY;vl-N}mF?`! zMO80!x=w2A;J8b*Gy)oO=~Zek5C{Jw;-A&v(iT(u2*9u|>g;5Af%$w3a-JnQK6aA1 zqk#;dkA+Xx__UhbYBHc_%Z*cf$+lMm+qAdZ{yT_%a(9sJpO!^pKg-$g1V2lJM4{%J z$Pgz~?q^{R@O-eVbdPA1#=K(MaE`H0nZVFVHS$(E8%Ds=@SoHpvEuWb*>@k~pe_4h zXbzFDZqjZz360{i0syZPun|z-x`r3D2$tCmqvy%XDlSSWXkI~X-+2zCnfh|PX@9eB zl`dg6wPjJJW+DK`@k178N)X_5zICLUpzhQa!OSh4>o!o>R%G;!(AbSci&l{#d=sX` zFZEU0WzfC|viv5%ySd2GV{B*l6Sj2%c&ub6NoC7aUx%54uxHCc-XX~g5CKFY4H%G9 zTnlpaS7S}h93Egjxpv@=X~{6Wg!pjE=BWpfkj~LczppN0Y(t&Ih)P%SS29-{1ZHzw6{*n&Og_9eVw-*CQV$Y>RyzV-GS=Z9Nrz+L)1d zK^p_$`1Ac2a(z!gQwCm-b2&^W6_>1ALcWZTZHIl&g@9ZyKQYs}Q^A+7i5#Oeq51t}?8%PacmM?!Br*(xttjI(Kc9g1_7Z;P0&P%!nOZ@>4qyEbG~ZcLB~U z@!2F);0da%im-gtS0-%h0KbIa+Crz=lZy}zfeA$XN(Q;FT?r`g;5B7vk4R*tfC=Sm+i0VQn1AONuC8kHt)dG6$qP7 z0-RUDoWy2|)zStCOE;mx*lA~=>)9+H;FAp(?NhW(D!Bu#;A9d!>K`$ zNFxFNfmNX?X=W;~>0y@C8{K7)!`SY_a<8Q=BYZzuGxL@S39>ekNc(AtHR7?-%Enlx zH$;ak41WE2CN6mxF!0C+AcDpe)Od!-vdi#GD2(2X96|J46*RW;)l{gCd#iy?k(GrQ z8zA;y;;|KO0Xctb{Y>+T)u4kXje~ui`oICu-@*)!FxV$DZ~to1u(66zvSH`B>aF5o zvRfw_9%e-rDPz<8lXt?n0`t7LpAhFFY<>|+nhLP;;0=)VsD6(nQvW*9 zec(Y`3xHL$T<;7e(DGk+{xm(0tD}1To$4RhiZ9U{4>xz&4)7nk5MPqgU-}=%b1Jw4 z>(vxYB65x>Wd%0>L8?DBg}tB(ssV=udhuY38wl* zvtw4&TNB=vR*B6OS#Pf2VG~&Tv|3Jm8VNC*HarF`s0W?Jji?+#Fo_oDgd8}o#})>L zid;2-_Jt1#Sbshr`ibDn1HSD8p%TKM4~ZI$ysy-LX_%U#Kj6ISodIhzm%PV}RHd3Mu4F4jJP@&7>8p%^vW(P9&*&11fhVYJ?$$z;&Eb3_tvw>446y0%LBmzj zYR!uZ09z)jiR_m`pWj0qrS?GjO)#n>u20<)GXVBq#T;^O6HaT2X)&}?b$@QQO-WWQ zUEMhpg3(l0_S}X*##4{F+T!8>2^-NtgitWSn&mtby>*iFaDkkg=arv`CI+KBjnOLk~N12JH2uY)BJ*lJL>F*nQN0E*bi@DM=wBMyNo9XR}r1`pZPH~By z0p5MB>op1D;S|HJ6rjAs2ks2A@!QC(W92)%*x~6w_v5OH!{Tq^>b_^FeYN+&~KzLR1?KV zhnQ(RklQyiSg_N)($CqXF zKRs1VJ)2kK9}|Q%{se*D3U-rLwTgR2Er_jfRQnsCVn>r~$cM{v7bHLx18?yK*hH9y zZ*ZJ6Aip|;J##O7YN+22HEm?0y})s@$gi*t6&EA22v+>wRu0VcB~EhGJnhf-U)r9I zMo5)?jsmVoKb2cAX;lv5HI9ICS%B+nyU&)9$rg_AchUf_{-~|-a)iWEma|$`Aeo`6 z23BOk^oM{76`zE3bd|WK-RLxxS2X(D6%0$)A6(WsCn})!GuPRdqJ`$uxG@AUOSC?2 zteZ2fyra%OPop9OIIy%V563i93aWA;dwG_?JJx2s!~WHgjGmMWsgP z`1kaV6yWAkFE{;)-pT0yKUDA^DrGSYIFqKT5@Eg20p5js=tRH4UP|Kt-msBpI20S) zZDv6gO1;{TS*J~WuN|wyXT$sC^UXeskJ$9?syl28FmUzLOCVh(wCbvk_v5k6Ai#=y z;VrQ5E%@?4ygLn1G@UQStr%Ln^}U|330DPn-A(E;oy#x4A}B%2t9S{ue|Fqm=zNe6 z`k3xyJDBFgd5C7W^cfE;F{U*5O z&!VagZKa>tGusgpVH0e_n&j(_vjsGR&a#hYsJ!VwWkC^X?TEzKGt{&n=h9cO?=Y@3 znayo6;2OZSV4|JXx*1g|hsq=UoE}`*tN_JA8g5URQ6IWx^Xk*kjXqr6)okE3Q~9vd zN1snHYMB6T4Ry71P~GbRmRq(O|4p!=Q%MTlaY{CwGm-{aye;`CxQjdR4W(6q{=)Zqb0t8;_tR({&Q#C1U>a?;jqps zz2L(hH>MDSRZ=gwPXF6E7cX0fg8CMm&$#N@nKwD?A|@N4D04*8l=_L z4(FpfjyEfnL#!MXnVgJty}t3^;sbuz`+(rmoO!5@Ly;AsL8&3m97LL5AWX^>ooUnO zS4OtVP{a2)WKIu;RAWb)f=3XWQ_nCMTJHrhm^{Y|)VWAkWBz&DGgO%s7#No3xBH6C z8^U)cKrdsTO`(b0P#|8IYcjY+c26s?^ZmSpFsn@l#4H{JJ+e_Z7t~=ORpi2V5tCM{ zFxeE4p^jvKtf?#rnoX?y=&k^(ToFgWI4mM5-|`I?HVz#*K8-It0NJ)k+q`cR%ig9M zgU0I*JF4#Nah&i?O~`ue7#5r;^mCV=t9KHd_wBqFjoa-KNXk3p7unSaddsFh?Hbv7 z1w0NebjB_i+;fhE`jX>~wYV65b@suJDcK{5Sd*}Ep!Q>J$da{xYL!N}9mLhuF|7_f z;`rfL0aTc07uk&yqy!SWLfrx|FTgqg#R2)PL0fdn*5L9l_8^a;=yV1=dDY6AOvP@3@Mfo`N# zDxe#8^CquG*=!TF(#FR)?JNWo&2CzI+QZ;XdmR&_bPM~P9DsugZ>%=8PH(xDJhZDy zw%;sAX_#iHHkic1Oyjwz1_M$vV1@QTM#?l{1w$3!KaE{1?9D5Yw_EoUq~ z=}Rs#b#;Z{BK5aEP1GJ-OQ(pJLgsK_+t~@WMN&cy3(l?j8gQ0J+o&{w?Q;eh{lX!* zEpW~!^*Fjj3}wL`MYCc>VGfS|g0h>rj|K&^NhA-}>0O}5l=&dXHLdq|G^lbmt~joz zDUx#@vr2CD`^05lJf<7m_1Hi@W)*Trih1^Ybuuj4B@KUS!uUyw659e4+C?lDnpx^* zDmPFj`X84_te+Ql5E55}=~0q-?9fqBj*iyqqVCj&1t@$4$6W|pGp~a+`0grRFFa%$ z$~w2ZpWDZCt|N3&#mTd#LQK&HdX8{TQ+2Fl>0wlz*~XW5)rI@~m2D?E_xG_tkL{?dzXBW**?h%n$i)>7e{@7SN#WrO88Q&54x z?&mCl$qoPet5fyY0`RPYEhk4~Ub6emQ0gbtjt-M)8>VUFW!+o0p{+LF%rE`#ciUaC zZVf#pNi0jxA&L_}ba#>zQLUtOq3uWLnhV$dXEyi|8F0E=@0bn~#?cEX>`a6N{6WQ1Z<%h-t8_>=4}1#`2Dy3J(&4Vk zwjt%-#UvM#mO5|?I(nNoY|^Ba96g$POJAM{Yk z2Er_oYRiKHrr()Y9}lgcUbx=GVF5qPU|T6&Irr?Ep1j`OHMKz6LoGJKs`TVvtk(DY z5QKuwcXCETG%w#zE|oz~%yF8Gpov+v^JMlqDNfS5n2)iiYBXP! z_+WgD_$sAAX{N^)8Z2UZ2_6g%&SXz}Qm)YIFVn^ZjKbb8M>9r>yAo3yu~QRwI3{G# zy-Z|I2D`rqT&%-~SV*i?pF7>7u5r18_St1ikkkds1f*Va%oWv&;dD^EZXyP~H6o5T zY<3kk1t4w$vwg?$%_)Rnup-2-2o0J48xzcY@bJ zE1OQRuKsW#_Xl(04hfAK<~A)UR%+1gTW#E;IRTGBkqHH$<}J4bp_Mhf2>ik79FGClISLbN>)uv=V2wJL^&w6 zmjXy^eUTQtA4mjy^%Jv(aS{O%IzW>Fd5LS*!VI&(>dnA~JwyZZR&s?s&eDSiFR5*; z1xCoMKV`?Y|3==>*I_B+l$DLcLz*@#C7`+Gf0u<i|j`rnsA%b z)xx`tip=;-Mz&Sy;^L6gQz@H9!YqF(`KRbrT`9mPP3-uDMqu;0gMT0mNyRNo6xbEp ztMsjuN>xc@K=H69i9@vrxxla@3NCf5@ADBEa3W$(g#SD{=#I9VWD)6yhQ@f>eGsk_ zY5{zF2gfQ_eY3nN%hHo2-xNw1?bFQI7dRfP6fS;`?x(|M?>B(X1R#wok(GtLlCtnA zGJ0uLlUn@6r_VARCzSbV8ac}qwN8m5ne~v}i8L#8GLgW554d@r_@A8Qip7 zvq#>lGY%#yig!qEew9amyg9X+y(OXkR6r?OPxJCmv_CxKU#NU+toufk&Ziq8`eYgo z8ln=+rZG?#d5-fkW23klJgBAz;10L_Q%oImE_<9{$Pz@_P+C<=C()`!+6H8Tm;3E*8b8NDO_* z+!b9;0HqqrHSm_)?9a?G783Gq=Vk84)IM1cO|U30Q*kTR*yQI8KwMhNjZqTRC7uP- z!SmEyYf=G!Z%`JcE9Dw!Blx72#mMHnuYiNHPoR2sr;TrVI}{G)5<{J2;u|<1xtW-` zRkeQA9yoVl2F2U>v0kpm-|Lf$9u_JADjmK*xkzR!>wW=%lRRWuwZ&W0{@8s%UI^)C z;Sz945GrpI$6^=93z?hKam+swXM34^*TLaJArle*pu6!97ve|isbTv4#X>4}<|(}+ zO#sW7vg-y`@+S$U;x_m3kD%JQA5k|HNqp!p25K`{TMY&W#|}bc`092( z{`z#Wiwh)6kU}vdTd6%h29BD+<=w9X`G>nw_u!g&eRf?CVO{Ap=FkLqQMp0ougwvl z=5}^Wn}v19fUM0h_&*q5gSUL_kX+M9QeSJrr^-D_xgN#~y<2*qh$lk2D;{> z&q#_(4#7>;pprDea;g|c2s^IGzQI&Wf)AZ&|Q3p&HpK>L~9H+|x%iWPKCX&k50 zsJh(3gKCoFprX$(=J9vROZO2$EJ3nq@8!UPSTVWBG0-fY6|nv+Bh8z(Gcn`~gSAzn zrhtaHMsCB!7xZn5dg;#Rw^-&Cr5LqyJq2W0zAc+54toV?+W73@kpRb0F(gzZTwsI< z0$&sMq*#{F?2u|4>ppjB33ZJ&D88RyY8 zC8wQ_jk9#P8Z)#*m&xqh%&Z)ppZ86AOZqWB3L|zr?|0LXZsVNj9T$2ySZJPL$x$ELm{FiI|4Bi zJ`KGSr33J?@-HXJM1HKx?8U30maoLntCHzt0IS#ZYjHw}666)oDQ~zO>wzX7p!%Ho z(g=pRzY}!Q*#yc)pd)HY8Dv)omlB0Z#R{xaOxhHXfDr$w_m>z?6*3aAeOL|C=*q1M zEQ9O*1x2145TWtWTlAYn0V--M24R&4K``Y%=Qgv9oQJX30%V zme8uW0pliS1ufd0?a6@AAVeELDyJZPphC0c8eUV)3(r{&4WYkTIF=7}+m4Cxo`Eaw&LaT$ z=r-8~S`L_pCHy(eL*$3DNxFUbMo82Aju&H1EfctfRWlh|+{&E2MkRMaWR<)mXEtRp zZcYnh+p7)2n*I=JsYE9m``oF%;VJNZNQs@1)hIWHe?^2ztS-)ff0tjU;mh2s1j;vjlfiSHy!#HYWgCh1aw3L z@qTCfJUv1CPw@Z6tE?pxO9M!c{FbfZjuKFKKZp&{C1@>3|60Y1pA$)OYL2J^-)OtQ z;8P)ecoStUlI^RRw0M@cHr}@nyAB<+n)WfTLTwmn*-v!YO!)zW#4#=7va}O+YiR>~ ziQjD#h*3zar#S0GocDu13@cDCY^8IQjCZ$)nAoUp>l$@<_%|GDLsF!mTS=;h-I9Wv zVkBrOb|*5*k+1O)OYpN=ByIV~MPCn#rLza&0G^!zFXAdxQr2_d(SHxn`{!egK{DQe@oTvSZbxz#Zq8jy0>>-$xgKY0wXpy@` zgFuvb>|-4Te|)Tv1j*exs8Qh6*F>W7gl_|6+>{jm6{273B!^O zHR4LWF9?d(_wR@ugnfBF4I33T*LF|V8}TRXS(B@2AFx;w3qbtXxiX{}$>6 zheC2vAm-|_aUbj3*p28m{ggO~8f3bG##U~LF?tXxlPG~+)VNeWscEHGBzdmt9D8@q zV=8I%{!jJCjg6~7JST#V5&d`GYWdVNdzgI9(p~xbYoVyYOLlE>iPiajDRt1n%6Y1* zRdJA5e$)a$_(!JF^-yCqOw$N>!y(KLbS}6Pg>ycAE8B);1nxti2x8rzM ztM6B0Yd|jZdDu)&Ss54}!EJLjFa)zijU604TU{#|lX?LZ$gRl%Y^9pG!kq78???1- zYJd6iR;M?p;bYUt|CSn>=)AQZq^1gMk@24boLrdo2xUjF)DyTn419+@kvD_v4NIKs zc$4+IZZC~w!ub#v@FS+l-zbSB$LLDd@(LbhgpJo?z1`PH5(x}cbz>P>JdnyK2;3?u zAq1>Ed=q4cxr`Q@r|PHIvKf@myID@&PN^wh6YLms-CLAhMa!n4T1Tnrtd;gbYpX&#xTUEf0pu7)T%YK#@|BscaWt(XHaJN$b6>@DC~b!4fjl!fe9(22ig9e zVK76a-@C`LP+-)a*0kq-eG5ciy*4^c@29)3?#2Z2Jc7e%}K5Ss@J zYoY3MJ{+=cbb^``?Dn!^z-@mAEawe*9s7o006eA0ZWYRDyxB__;VmZHGwB6|ZYogU z*cA~-0FX6a*d(Mp)Ye>ts7gElipq;pBO#9s_n0rxWZ&k#i0wrGjRls!4JzZ5@lURZ z^ly7%>(7Z$vD(dUCsJ+we0VL%9S>pauCE~}5klt0SzG1(pVI^t<49H=3*TB;)w;n3 zF^XC_8Hur{+_K^yy^hk8>gHg9nEZ$-qtYvf^gq5dkT`HBnAgZ3Vy$tLc>;t+&6*z5 zL9B+$b{&|c61_Q$wWPebM0A8?ml}HQ2!V7K-&f6k)xCk?QMq^2%DjZTA%#e9Mo`jm z0!aJss;kVCu#Ns3wbi%T@48HpVY)N7#yneeE>^P9GjXCjW8zbZlnRTI;?rgg9)FSTVv-)=41#GPA>?Zf6aQJyoQH_GFR(`rrir) zVh)Tv7ncpR;?#Pw7=!6Rnub*&*YdfV*9~!8u3dZQq?KUS z$SXV4L%!t3+Y3d<`Hy-xKkrt-A?P?HTek3zvHb`T1_t(fNce8Mz=En#0*GUNe+*sa z|AOhPe)4?$<_6DK=b^}NyEu-Z?L@k^+W((sinMn0^T6oZbU~n(8L6{X%JHk|@X#P3 zYKo@N;Dt7;Pw=iR=;uuP|0m5#Ps zoBPV+kLs+fK@Z?(-+=;8Ikqae0+)#4qjxntlayJ43$41?iXQ}i9AzM<>4v)DebJey z3MZdZW>e}*REkfuEXN(2ATQ3p)h7mBkko4o6D__zr#+YQM@BZvgQ`GtG;<9 z$2y^O8JGNR?>41%Wf7rn?28srJiF~C)X{KdKGN;(HcN6$yV%?PK#bnb5_OR)NE|q1 z4nk#nF-sk#LaeY&`0IS{pe9c0Cwa;>(nB#67Z4tskg5}Ql*{oX2q6*yY}Z7PWHv1i zM3w{{VCq=7(iuHFfFVd$qWi`_g7Jg2rDqEN{x3NW)SR?`f zM?kp0&q`>bTOaYUbCYvJ{dmYF%jl-Ay%5;N?BryLV%00%={BrKgF1F54vI=giyy%6GA_|z-B zwH&E$1qQCEj}`%R=uAS7>DUZk^0l?uhmpMV2L$8_LiYP&QF$+0YY9G-6uDR>v2QQW zOX45}^!6DMio4-4g$7j9g%$l4<1o`;!N`E2^Wfc^csk-jm^3>?H173ml9oi|a##uIvT3$o?g23Q4?wyudC z?IG};URoE+g`&UquHPrAblRe;NY{!99Khilb>d&p9VB53pO~ zV0MwuwK7452_IPfk2FXy9~{uW32s1}FEflH$`iuLKj-0G+ELU5;!Jj04}l%-bgKE2 zH^X$DV&bSPf~l(=Tgj=g6Z&ee86CZgJg=W;Hck!ZB;KhxP^!$(W;~VatGH#K0>j4R zEV=G(675d~#`T4Huzx3JI8A)&s7W3Qx3lU)HW6ucT#16nH?H^keq`HDm}F{SbsPM& z+rCyP!j8E6f@nhTxR;d=g3jy_-i+lFHl$SglW~no>9cNz;%wqJ;^JYslWS6zNt9n5 zqY{)WW_hi}NtF9adW>WCep<*5N73+i6(fty5LV}A0T-?9qP zizChZ&!6x+G=+4pb1=_ML#$P^SNKYPG*w4L9x&4jU5!Ec2-%^E%EKh`oCM^$!@6R& zI&6U_B##D3hAi)_6rpblp;b10GI)3_C8cBbA4OHo8rUS*Nhoce=;zAwt(;e#khKTY zi`eHliAl_1qd^k-XD9;ZJDevzkm4a4Z4Z1qO=^K_)0ReYB+IN}DzVzw!(}M)qFRxr zW z>TDKGZfkE_j{B8L@n&%G+z8108b5rnFtAPp*C#Q7;1|Gkt0UM2vNPvv`E;8rV>J-m zPJDv>&P@o^w3W>Z7Y-=>P~^8x{)JI*WXZZVT1NftZ@;V1ABqSmpP(SanR0>T(~ApM zBd$K2iRW8-lZI|r?f3cG!u?Unn;r1&ck=B1aG+H+W6PF`O5bq0Nt;vGZ^t-5D(liE z*iMMd^@rw9S0#H9Bru@nK#KZqzzA3Blf&LFl zV+|Uj3AP2Cgr-Ky{*MUDjU?EMJ6sf-ReMl65D371(mQOJta4j>^H8I3Omdb}b$3xK zjl_B@dB}%X24hxBSgKHZ`V1f@nsZM&?PPNQ$VHhy2cjqG-+Kr=?H+&PtWEO)#J^Xt zg}#6z*vb)LYJOPOfA!_&0}E}#4_?^vWHi-b1k0&smiGNR{U2%JpObo-ga6VJG;*Fd z6E@lYP)8|&;xb%nqm_-a!M~r7_Xel=bBa2o)8dUtBFK44h~Q5U{2=MbmiUGkdFeLQ zEd3O&8ah$nC>seiu% zawwza9d6lHviG9gRI?tF*n9^Tu9&wTKhm~Zu@0w#WEU%r1w<#%{|L*MAC36Mszl+L z0tM<}VPHT=tOO_H!yw~TiKCnOVq4e8?3KCttX~n3Jbbb+7zu6WLVMzi3zDrxGz=_Rq~5`K8B)Zl5Q>J}!1j_r=b2YSL4 zoy$mlrr;}igKc%V^iM-n^|53cd27r$Yo6TgTs@oPsRozlEaXbqWMB-Q$oVySK># zs-N&1*Deado%*W+k&Gj7oEs$srR4^FHmdRU7wsK~SZ#g&PsH-D#%XyK$;~ zlhbvAVlMP-SaV7II?;hYKDvOz{Q<1aS`6XwN^6VO+}yd#uioy=dY%2W;g=tRBjYrT z&a|BS*NEdP+8CH{;zv>UdSTEoR~!fLi{Nh?c!k5^Tpao!zowxGT6f+|28~$SP=_O1{bwp5A5-9`cEyEu& z2jklDop*oRVHz{0XH(CA$C8pJu`7yB8D9P{NVYL9BWOB;v*3*sz_qixXm~4CM$#&{ z;g!G&Xq5QKxtj216${OUB!HW2OzL$_#e)X8a?)XPgss{(LAPmrT;>JS>Pf6m3AZ^r z=MXQzkpQICHG^tpYg#j+aq# zk$}NsfW(JFSpod-tDCIpzj#`pX{20u7erI<%6op!Qf0a7dL{w6>BGd z_oezfYu}LNebr3N%x1GXKvJ4QzcJ+oZB6?Ud6`4cYIJRn9XvD);CI2D0~2fa!{oEK zVqJ()qYUKb0E@auO(eL3=_x-h*$B$};A_CaZf`8wU3(aT*`Ap4GC2IgaqXKxOizxGfv)thF0%#66Gw&EY zhsMnk9e)mU?lp?DKdT8utm`Z#hpJ^)|M%(;woIcP`0HV9ekHUHR;dk%FSIyp<3wlo!M8~@4Aqx7kHnK}or@r|5R@&l${XZq1;J$qV+x;f>IlB3+ONyV`NBg_J$^rX6cGa^| ztaw5FlOU>5Le~BxHa?}6p0OYP4*YiPhwOZM5j+inyPMg7;-YlEw~+K=`-=02z|X<* zarQ_|2+#j)c#V>9@of%Ab4=9yDxN4u2#tssUWgi1%lf$E7XJgz3!PUIpRw+$U5HS- z7J$DHN>bW=;&{B=v=eL1VhNB%_(!DDj`iqPR(BDoc6BwHf9HnJqpXEYa{`NtppgAF zj+}>0Kh58X>KajirR-n7(|FxrNzRz~8e??M%($%D z<(Mdg+tTSm$KjQGlPAx~j_NUmpIoyTcex}lC81i;&=sZC4kx~D1O=$n1#V-sJs8Xd zkfQsUyG$Wmzq~!aKbXqGSN0!ae!VD2^5LD-;m~SeiKnElXa>Y zmB||tujj@6T*sy^wQnKbaex~B0FMbHArzDM zO5&M1rz|5Y>%jT^!`6OMr+c3SdEQI)A$Wpgh@gar&ZtP@qH= z*y~Ymfq{=;{Uk|Lr+5(dBI-Bt^YzMp5W)SkVC}r78|}HYo}fn52I;IO#W+m=2}={I zFYWf`7vVou{j-I*WV1u0&31NCWLf?a-g3r<(dM=l7JIx*VC_0ta!l%;a~T0zYTwep z8NQ(<9L@Xs)~IpWxmQTHxjtS+T|6EcETbJPp?$Mp9Rc1~QlTT-Os9+*B>{y#uvL(M zk>`keIha(RFyl7BH4=uXO%fC!m_hr<2>meLqzm#IM%(qJ(Cg;HA1p%36Wi#XNpmPMuEb*;|5@eXF6m`|N?Wy644LCd+q`R@w!z-U>(mKf zARdH4q({qqg&H>7E`gBqaC8?!^`cGzE9nUA(Km|PEdAy^R`CxqoP0u*ur)<#Oqn|g z|C)Liz?S_1%Jv~4&_r(xGYXX>ytb6LH`U;K6AYvpD68xBK zO2MHD;9MnYM!dV8!6WZ@=}Yzyf#aQU{-*rj7i~809UUt-{B)Y*8XYm-Gc&$|aP!_0$1-bio!Q2SemIz*YW*?vKUEflsMYv$>m;1=Mx%NPl)gAh5qZEv;KBu zTO*K#!J0LR{fF_-*e z2!GJXSq$L=?pV)F`%Wd;N*v7BWY|FEKeSWPfP&|T>OoZ?ygwQV{5$*qYUI%_gj*6E zX^+mK8GNpN2?^HHcz|3I4{qidr}EF>J=hJKj2FY7MT4kA z#l|)f&X=??JH9^zPcQejX%W9__Qw-#*0(x1LJcn~Du>(Sz6aU&XE;~?IF?v(D6_sM z3!U6_uc9_zS-n6+Au@sf!vYnanAhN%??*QDfd@51&@m(_YBqW?fRpn=uJbCCVDWCJ zJ5dj3!IXN4>Xn*9##$kdl%cY|7;b?sv1nIk^23@6e&6$*H3#gw>?qt$O>z=k=&?^aR2r3taY>(4u+Q=4<_Vk zwsnxn4LY+(4l&w5*i6VU1|<}$=bnUr@KFv}AUQaVkDk~);sonbuf}Gl7V0g>BukH# z24UCKR+IK1QqATaOQcAIv3HzhgSU=c%IsVc9;9B#7;vF#j_hXD0|iks2B0vg8aLWC zB%pf{$V%-~wJbW8k4ygbY>GtUC))M;)@tU6c%~q?#H4d0pzJxC7$CkL%WRiLq|{Q_ zHz{E}by(Ba_|Hx(;JDk!072Cn8}^N$BHD&O1x z=-5_vHtUA1COZnJ_$}dgtK%dZbZQo7RhSY11YFpP8LR8k=AF!H4t{|qbVd(Dd$jp; zwW#e4bx?ktF=`zHoiPo7H%7r|-b}+H0gk9FK?1>D+l+gxe+hyfaC38>Z@QTr@8hKl zA1IHk14_w)(DU}Rvc2_QKfx=bemmd!3$A$RZ_ja9#7>>8S-_?L7nxYpxyDuA7z=l_7!+v%_6mUMVMttO3gwG9LG+qL1ZM z_YjJb-Vb9JF!n>G)g70QF|-kQFP(&sSg&fkh@AKQ&#VnM(lqHAb#7ZcBWAnT1ENM^ z6Xh*{>lE%IYGafpgJh2zI=PZmQ>Ij{=}np~F_%aTq)?JLa=~=oYA7hhBZfTnmSqmI zDyNMDVnjG(r%_qGcqD1=jPfrD;J_Vt_rWCk#c|*QrqHMaU6&Ic4y;BU$0eirz*2Lp zdN>udL?Xpwh_Oa)Hq7|^#V3h)+6!zN=4(x5Pd|~~Uw;70p^IHcg#cS*g9aIwCVv) z>E9~U%y>!AkfHeH?TxwsJt*$@p3Dfr@>jIH_eD~&h^G*hNJPfu1#x!a@vAFy>~;t4 z7A78*&4ll1VIdd+lZdr(!`(26?r2|vq%n`@ACtxlL@i$^Gor@}#I(J0TI21(0tT zi=Mv_HwzS@rFI49#?Mo65KZ#cLrHM5KQm(-$7=3a~}uVG5Bv&Citl{SAnTQ zGDGZeel;Q*ICL<`U@j{zMxHCsQn!3yWaE*u)Igcqg^)>6SersUE^^U&I|G1F+2rxS z+2aJYZnzt%)ELvGP9xlAE;_CT6x5DHpi>5^>`>6on6V5Jt=a?15J3$mm1KO&;YEze zV!PDRC24ZPao|ByG&H%sAp^@>lL*bCvbxBP-ps4$?>+;XTnTh77;@lllK502%ju-_ z1yVNZkX+;uLKm)zmCxw7bO?(sr{ZE?WsC!ESl<*ruS|v-ftSvky!_dG-{nSKej4}t z*#=m5WfPK}a6)OWq1tHPDcHkAE}TUlCKX!@7RLD~q1wp~(n4GUg(C&vbTk(`l{CBj}BIIZA3WjrRi_NT|}#bl}Wa8VR9 zvN4Yhu0!B((2_!%I8-OsUKog{eW%B4d^ar8{Lps0Db8)fGHwu;W}LUGW-W@w0HJHf zSOf{H4X<5$P$-&fr}G$Gm41IQ!L!K-!5B|mCoZKu>^F;(nkGlxcq_Up)Fa5uEMvUS zsNjUjA!mV&o|>dr{!nMEszm;Qok}R#i4YHi_%?e;IF?M}g8atlf(zP2#kS=m$#XQV zIf#N_6x>{V`>^wp)t zL}4^@UHlO=gt}KejVR8!<@7P?4L*`IamCZ`6WE1Xd2Wql$kE5oQ{AqgE=DPKLnTPS z^<$tQ;a#S{Fk7r9r@aWVOy!?~8UW~OaB;)Ro8ShCjgn1i^jP8tGVfgCY6z_}T(|wcR+&tvIT#9Ri=M30Q(ZhPZrX2z*dD05i7F|+`$eK_nZ|9MQ z+7WdTlBfA`RKffDbriL|P6bQWEBo-q<(C@8<;y0?Vi~(cX`8YvEYlbw=P)TKV*J_J z&OZFQ_S?@Oe^xW51o%h5mZKcNjV6{r7_iPo*RlY%BL_6?il56c(Q`S2F8{`MM~IjC zj)bBiy4IJJ!QtVcDO=jHOWqZ&J)@#G$mHRsQZ#r0-7-0{HxZA@t|>Z1O5{zzPB30B zguim}!u|2&9nvY(IF@uGO@f2RWuH0B6xW3V!jYcCh8^$k6=%Ls`n{UWpw2EX} zaq!m;3_fYmRgA7~tjwJoYURf4J?h`HQ-Ic65q+6WV*u@apR>fGiin5cTx=*+rA!n=_t} zzk|D$_C^7@h6II@UTd8f7<6j4SGhiNNG@)K=+xSx&^6FIx6h#AROeNNE=LkIA8x`0 zEAg~ETtz(6PvecYu6)g)8RjZZMH)8?sAv#fiuXU_MP3*!)tb<$)s$3o6Km^7hK%(Mw^p zP9#4|x;}@(kP7lgTvN-FD9&+ZM7ekoi(9>?rSB=2Aix^i@YUpP#Ie3i+G9xDGLYO= ze~Y7Va%<48(L;xNOJQZYTr>192fheM7#AIFDBiwEy=ECv3H<)yT?iy#aP`cYG|jDk z$YkG>-xt@NZ8P0|yCt&L^762*|1C(>bN={u22bi>5GB8FdI#RB4u~Sl5$!tp`k@Zj zn%j%piOFPI9d+p~;vo;R+srsS( zO`P0~KUqxDF{e|;-cw|>tr^#MS(Rd(M6X^t`U8xoJ{yc)LY}r$(NowxTS>gzse~QZ<_#M=iftp= z|K2+`!$fTz<&z3q*9l3x4%@PF{~ZvK5G{~y?~JM4MO8;}pz*}zcltZ-#e=M+R>uTr znJY3|hdeJ&9(q~K)E6Oj+l&i>+lamMp6N-Wd)r@g2(zU&A0q$C@^vX=jkW*$38nbN zBsXAq7u5x&{4^Gp>fbmbo-ru8h2tH<$As4~=R-EP%iz=6eZCLBA|KKD!I7aH3Gn^( z%_crOOnKX?6&lQrWZNLr&S}X;?EZ1V4!wZ5-FTdPQJ{ucvN37=rPwk_e zunooWDs#N}<|1fk0Q9l(4WFjAQj;zd?mJ{nSqkP*m91+26(5jT*AZ(}!^=vbY`#Ya zpVeH5P$8*0@Ml{@;M55!lELfT>N&HF$He{M2lpEaSufWC;eEchBeReKid)W~7uT+9 z-}Et?|Hm>;6r){k;w+jEOUAL7zLj?}F>L}NUq~oqf3zB5(Ci(r3*l7kj{;XMVCsMU)X-eQ5(|o1vKeLKdy1#m$|i{iIzNl}7?A28)p`>^YL` zX=9*{zL`d0gi_JM5C~P=2B^&~;-*%|xURQ$X4n?TGnTRE&msSlzM4wvDuzo#)&2ny z+QcG@yHTs2_&D0Iy{o{{&^xr1YRf2Z-6rM49VWZdj+*|B9UUrCQb77(O}excHr`Xr z?YwsEs!cY#Zz>^zKUdpG6Tq*L4K#6G`gfTr2v2Rl$eBV>NzFSVU*u;*?(kB(Y5DSY zb(Q8=ERbKoR^1|6-fL&qJY$~`K{^MZjL}_6K#D{ZOf#1WWiO{zV@DBG<(^*@!@K|z z{mNE)K2p5$Is9v$fTakL{uo|f1QJY)fsF`fr_w;~sE(VozUqJ`&Df$npmkUQ-a?kx z590s~%;+xn!H~qMz>S0n>)X7w-+1j}S;xP_0|a26^(bHiSPAYee?;M?A;W;5eC*F6 z8Tce&~9yE@E<5_=I&eg1w}L^Jv<;s zd5UYS{IhRX^wNGuc|t2c!bl;jPs(J|)=8V9eewc`MKC7D7&km%J-mGQujvmpf9hrz zySQExec}h4pR={ZY(ZMoCT5~g0zNQ!EPy5!%%Mj7sX*KK3SH9K#E9#->6C5WcsXJi z=rDitkU%>E)Fhgt5OpfqYRiN!8>0TaO|av@38N_{Nce;YnWQzG=8Suh^mC%UcC9(B zg#E?gnTo=fk$))HZ+|k5?=#(Nm(=Zj1ueLmkMHusDyaWl0f74TF7)?VW2~R~Tk_(x z!M8RvM?`;_NHACD=Y2z~vE z0myd?QJm5AP=a(v06yHbmTij(EReraRUoHpf$uYdX$FPsw>#nhFn(Mz;9QUj`~hQh z^Og{}+)yE2#h~0yBEAd_YjKA%_NUiGBfoaQWF~l}dKbo!kU8Dp%f_IHXM|YsNKq_c z8$;$$aHad^p-lZu-@qkpQxiQq<$QiNiT3og{tlN#OOaFi3ajALW9iS=P%z3OEb6c_ zQ7iXp1UcU_K~*gT&EV+U_qE`kYmCDURd3^>kmn-3_Y$*)1O%Mb%D+`V9O9S7*=5nh z5%hn7{4S|ut)9#e7P>~{6e+Y}2F3xAG0H>vvv(O%TMDKGRoPXvWP`krZaBN=SQdQ@ zlP@$!WMvEQt-i$1Rb4ro25A(xP42U;9KFU8}`G|kb% zeptdI$OCg2nHZtZY|(4|&ooCxEr(8D5s zDia=QJZNBO?#~~(f^mkCg5xFKue55GX;fm}kM*A!2qk0cD}mGRmv~>x4Q858z>Efq z)t)*E@U0{|ChB z%DPN=2%zV-5bT?pq_b6F4!5kjy#p2PcpCQSE1|I~6@ znyQ~1I4U$8HZ*&8SS-O#bIvoG+mS|evnni4O3Qyo9JBzl82UW8epk9PvFq6T_~4MF z?nxB#5lqb|+q%+e2^RIV7{kP^c(tIXbIXwRMSJCP^`O`?rc9!h#uKQB{cZ>EL5S#U zB)FvI8igu!FoT>5pVODU;ndVHE}6l4q=P8b)jdthqjc$R3jjJR-P@MnR^p`X`E4pd zcKWo~V^T0LxaOo*k8)d=b-e$*2*Tly9;h%%vzzRBg%NsbJU!MzdkuzRc5;hO@ca{3 zmu$cH*!SCJQV=~A-1p4F`x`rrN{+vM@P*J|tb+WoDqNZrbx{Q^+f8s99%C`hZm}2v z>2-DfQocf|qi`6{j(x!u*8Zk-{SJ7t6^siJoyN@K`LxdGdaUQpVb3?v=H>p}RbFW1 zJlg9gS{hCjNnr=KwGWN&4^XD{ygVbok$MLgScaz zvday#N-7^LTcDu0+px`#o{RC-4bTX}JU!CN2qHuVN;uil7{W7oy_ou?!R4KVgYp#8 zz~jwOdn-$$Nmytbejm0DvMH#eoU>A&OlpbbI|BVunqa|pL%(uO7}2*6pieMW#J#b0JAzx)id)7)Qj;FcFm-WyTVd>Xg-YCo}+o zWo{!SvDYct>JsKhrLOh@G+|$8oB*;tnFg22ihy|5s}v`g!pxGa=l(eX+^M4XnWZU; zLPuP<8JwYGmh61?`*AU<`dKfP+9NM}>6vm{j+pX{^1{z$~ta z5s#KWjcFYeT2#IN&ftNrOoG%h<|hRqW~7M@{&P|WenJa$1)`X;L~$Sy43`#mK~2g; zPMQvSiExwDm^8*aATQ%9Gu1#~(5v}uoukii4{=x3gTuHv&`;D> zD%r5iv?B3(4A2myWKs`*0eE%Mj={{~)Im^=)CWLYnr))#%#L^QERSex2R2;~$NO z<4;>Af+F7$pxZF8e~7QbBNqVLZIfFJTLIT5i26Tv1`miAMw3V{o6x8OX8RQz8> z&IK}LQ1ZM(;!8Szmwp_S`-ps50ScvQpiP_Q``~qyn($}h}P@1g-JcGFx;GGBt z|GiHMHVYClhJz(f1jUzaz>L*2WwzyCWJ7pXXpQP(v&=WrgMD)J0jU?5k`@v9&83jH zn*~B0JxfZy5HLt>vYWXU^o|T19r~(d1MOh6Rd?xsGmTzY>0>6z)AOm}>y`Ko>Rq?= ziS~BTvKHA|oL*DL@znWcaV(|fs!8p;qz?xG%ijQ%fAcim2efG_t1njg;XgGgwPL?U z5Z0RFtffdInp|l75Bq9^nW2nLEJtFoxTW zG;TOP_el=Zd3o(93&P0El!F?W84QG^YZ3^^+(o2%hTf7F3#!gfbjRYfkUeMp-~|8O z$7#f-(F%l9`d55H=bOfT@W*HWUN`^kiW|}zL;+rCZg8=>g|86rfaO2HG@CaQLVU-M z`pKewLq={#M^Xyn;<zv#}D^7AmNMTeu(+lYBmtfw>o~kGj8wTJcW1vFW;3r2v$E ztzt`T$Ry#hu7U5y4s`hRk^*dUYwka9l7fIDYZai%RKnXye@01CIY zEnfP_)IDK}rQE+)`imZnjKIzSNTEQ>@_%*6jhYt)*oAD_Y%q{`k@{Q+Zfb*ftZd!@ zN@1INybRD!kkYOb&kLx;?|pyee?zOwVrbVZ96Ek=`S6eMK&=2TP{@FQMbc5_t||Gf zDE$vYpFVp|XmhT&HAOM@x--f0uqadm`rQT|(rcpuJdU75trPs^aCG(_#vGg4gq99> zJtB;6sSaey!5$;wVdDVGR?iqHx zO1YR*SmW?_9 z(AMntM($WRY!H_BdBXoN%ZXT~oTB#>t;%7;x$=tEWar5eKp|LB`g*-MG{3U)U4hZm zHB9e$)n0%gJ(G)0@{R{*edMU*nn=u>BqUU&5Y*$u07XAknwS1pIG9T;g?Kd|NEvpq zer{L8wiOj4i`=`s1a@6xVmstkNW}vt`hwH(+7=H-v~qFo5IFMwkzQ-H*>lCaOWYU zRMIm=DeDH0kZ|(8W<(hkdZsEF>Bbi@oq2$3&c7Nn=WRA88dH-wV8C_;plefSVj#3b zIO&NuavRuAE66{!5%Y_748vm8_XN4>lQ`3-s)a7vFxWnuX=8MJsTzqV!7*K;^}*|G zU^rw|#680mmTlr(tbdj9mY&Z3mwfyrsi<+|tBn?#cM0bTQA_sUX_$%n= znMEpB%*Y6Q9!bkU!ESI(+%k*+ixJ++=s-yRqUJz#&#pd*glrww|NIEFCM1b#Bw+4} z@wrTzNNoapkxdLQh{bbK2709)MJg`e!q@i23z-FA_{%Q-F3y&~m{A2ZP79AvnBODa zF!JIWv}gHS`5{~z7s{?T{`UDiMp~&djS&c(?MpRSQ5bAP<@Q25sp zfu({|hbXH$03?xM&8n{@xo9%8eq;A{i%q0Q!6k_9LGT6{Rj0p4Ms$v@6d%+HJg#}; z@$@m@yRScYwM_YxmYZH}MXNqtjj4^=q3tX-Mv2z+Rmhh~+?u5&GzWJW>oi?>X{@JF@mPdG!7gR9%}{RT~bwt>cU$QD_b zEGyICu@oYC23WL*KUGH;SW8xz*z)Q{j8=VYkkWY%yy+jMR(cb+532_E*ESHyzh}I| zeDb45JJnMRB8CiDGY;M545)~x0b?k#F)^K_OA+~?K+AK($+5&<|3Ogw@t1C@)~EFm zw4QQy!6Nt^ndibDz^-{91H^dgB@E6!d6L#7Sh`SCM{j*nUS}G?XxuGC{gLs+Z6v^L zt4!NamNHb10%8;Aa}Ohe_@uS*x8T9hL*a;ds0I}B>?;dh}Y5-_}1{Ri{%^V$BDQ zlohL#%q^%{di5!9{X+CrzB=)S5>WH2;GG?2VGPpOH`4{lOtZFu0&tBPF@YXcYIoN* zBW{6V-l#(?8YM#ZIq#KU2;I3}#TjHNLXQ?oH+2#M0n*q%X^QV7ZL|=JFoIf$Qy8>i zKHU6?=1}w!Yp9|k2qITH1&kkJiI^7G-Id08?Ak zAxf$MCI;L4fnPIy`RIw2iEs0&$%W!25RoQgp}ucUZgrjr?Wh|&FNv08Ba23i z#(nE`!LJd!fhYmi;>`8qiREy zMhOTQatZ|BfFo19RLvTjVXQOdmdqO8q$S_@U~S?3e}M55naSU|jpgG|ZB60}N1uq0 z5YATconEv z+4IW3<@%@LH0`emgC$P5Zd?cyM{but@T#jftgxZ^%qVDLik&34^_PGo@~6sfMa`5! zdq*@UBFmaT>gx71ueCAD@1;wBQ!X^vlK|Na*z-p#H3 zX_yiwcb81B``1kAUn7EeneEghl9DRoUnr)elhWpQTfaSI0C&Htai92R$#~Q8V&PHS zCV|CS$BIk?($B;D{iPaJIc6VQT0`+RD2SSlrwcLng+A6Lts!i#tqh{`$~@ou8T9VY zIGcLX*>1nUR%FVI8dSZfNAYz;$WjZ(Vpb^F0`xUunE2tEp_Q&dW?sH{!e_k*TbKY% z&FEm&-juWqMp!O|E^D=+{JO5Y9{aseEA7cIDJex~aMaIqH+r=T8f%x}fXAzsdgX_E z&Z_B_p*3trT+B{*yDOi86LSA{=(2wv9{Dv?yNF^@R^knem@BOUru`@Tre$@5^VI!= z*UXm2XhWb0WNdvYPIZyuv-vCqX`*&*3>s)7FRKPg>Jx$wD-qD~0e7;wqUmG2pqHv) zh=ay+V&wcul)zp|-3_mE1tP;UpcC3XcSrnO(^Kg)P1-FhkH;f*Lt|KUGQbGdRq=Xr z;arHQlSYa&Y^@ui-8Apx}XVi0E_%t!@ZftYQ6a z5%sMKLdHFp7h8i@locyO04(}98|~@cu*LokbMk5Qz6(;y|EpE4#YDa%%AW6~ib;*+ zxJ8%GznqpoXi(vyo9Du zSbg3ACh1Lo>7wqHbe$^dki>GQ@>Q3ynKEVY2N4{2Wl6rV8ELYC^I_$YAYf;b}7DAFhF;T7lEQwU(DbkL*6Apf^4tcNS)#A;(g{GGq7ak zlVQx#fPS6@B7|K=Tz(cDeS1#5@CcA=Yn4*@8YsjiR$R4e2>)w$<{_z*@N4*L<$HRM z9JaVVA*jt8AjfekQ%uH2!G#5}q{{GPg%r^5x7VWHDZ(HnK}yEB$1~<(-G0?~^4NZ^ zm&(n{1Xmr)3ClP|b*`MsV2EX}#uOoK7LiFcXHbRSj67%qG@q5eU)P$)hLMlcoC8Z8 zUFA3V${LklSfn(W&L6ws&6UGr6~@uiiv^ITl(zUn@42qv=31ZM-xIqmR@zB!5@k=@ z7RUv9JeE|- z#=z7XwM1~z1~CF5X4Q=}9XU(urN{k|mT`|BL4Z2?BkdF@RU=jag571-X%l>sAg7z+ zIcQ}01)#X!0Qk+CUfUZpaG%822@_JSV2~DZ>9_j}#D(J)k%l9AVCmoF4k$o8HG_m}#~u$(F`__=X8X-hNKw@ceQ9n+ z%02|3$|@i7w%|y_N;9?ZAk!AzFd$M%O5|2#O?COaI;_>KMv`HrpW}kVv#lpMm~Nj| zeS|8Ty9>_%N!k+tzpNRd-CJJ5%0(15BfP`mqX-dOHVl3x^S$jrg*RLonO)ci15D}m?;X>Iu}*_ z2-Qn8q7tu>_t6X$Co3H>MPdgPl87tU{?P?dFneXSeVGBPq>g&R`n3pn=N61>Y*^ab zqhL`v#(v+fM$7|@XP%b+WJHS9o^xb|jz7zZ+)S%u$lH zFQ%<`!a@0QvtDFPJNjBN|Iet~wd4jFnJODGjGusb7NF`vmFd;IG66#7zjHL>y$~Ryv^cT z^~PxNz!AqrlfZuNkG83Wndp^8v<7sopGcknJ3z$0QVsxqs!srkUA6R6qB6?*p+n22()?Ehciet)ago^EQz6fO^}I zzJWWH%J83bYWgyBL62 zVqIAlS*@I(Mmm#$Bk7YOO!AAw-=$*}8}|I-SJqj3K~XJMGaS=R*(Zxk8F;unwT!6e zOYb{*RUowJIxW3C!T7uUoofQh@tr&W{87;P@k z%Rf$mMH~LMxX!I_G%=zwO417d1oj)zc*yfyZB67%(s|Z^C=hT6!UOJpjAxs%5p%S-m^>acW^=O8DJa8Sy!p#{`ijv4=h}Up$ zpM77Oe62|2?OHLgQ~0~SX7EVVN^C@n?qLnwY}d$k$E6Ma=GN7!4)t^KIXrqpnH&f+ z4x7z?>*w9Mq=^eu@E5sl%c>D{UL20geSYrQ#yT4~Io|c;GB4(&mF9h1>*H1_gi{?( z`GXB`F%wvf$qH54^DSWxNAb5Vxa<#IJELFLAyyIWdJ^_B9dox2lK8s2bXu_r5yw0Q z1sS5l_yGkNhD!-oSo z-hI0B|DzdJPI+A3R{&I{wf@5u3DearbxmBd6H-FMf9r%a`Z+?gdLYo$)PWyDuyi4r zZ94@2Mn0JAG(a2&8e`F&=HACwaM_RAT}trUC&V;>)@B!_$9`%t0(DF5BL#(4`S$HX&OibR?IlsrP?Ur_icm-tqNi zyGvT6v&{&9?BxZ@l&zCO25K&R#W$X15Bi4AcRCJoud79Q^jJe8?8q4%PiqLtW#v!l zO?pFlkwIR}ta*4C6;Z;;UFr!#(_Ip_$+$`%%D=^j+_?`r{oy*Vi%Bi78D{C-kRQyZ_AZhZ*A~4= z+Kam0+RtV~*+Js-t7mmBPs{N+STWX;kA|7gnYHJVRo8K0hLkz1grMDBaoXSOyot-1 zF7gFsR30!4>lnvoL_nBHekf~+7s?Id1-)C6L)~;Y? zzc2Th3$s*%kM`sBeB8aq0Ww{@n>)(LZTW`u2Ux1<;bGN>?t=W8|NNw5;+O;?7={qV za^?CH$Eb(-rQ0 zYyDOAs4gc@gX_ZlWZ$2SCjV2zji%bca==SAVOGXuz%KThWbI5I;f6nBrD2E4Ig*aR zS7RhxZuApCsv+a<^YeqGQPuRPRJf~b2=}xLZJOi1lmNhaV0XeBjP`)pFieDre`IZqbxyhd`czgz;|r(6!nM_6z`2G4asb1@kQqgRCuIC%m6eUV6^ zJfAS~yzKf0F6bBD9~&jkR7|K3J=;cM^l!kZjgDL0?k&{Ow zQz=>?Ldgkq{4f z0rS9Lw1n+3*K^G}QS=ebO<8UpQ% z9}7zB84-3cn{Cx{bL_gpig@ZwUoLOwdEWhe+^NtS$6JVwvyffG!{&jMm=uaFE!T{< zfh6E|4u+NW58?HR8@~M#wl>qj$xs@0a`B`8a1Tq8ANHs+n52JcYx_ceca7eQbg(iS zouK>$$uM+;ehJW@5p@K1JyF0#DG+d}L!?w74FjUkpxIBL^M%M6;JfBh;otK~{aX}v1sbnkRgPu`@>xCphOpr}=6E;`!DU%Xt{U)Yd*XME%V3xEDK9ytNjhywmNb(_LXCK&#mv`pOZqYNzkVdfsT8 zyQjO@$( zeLmq$I{~R@a>-rlsQ;3T0rNiSY~r?5ythwuQ~ zG^sUF<{lnpfA#(uF?Pywku+8P(UQJkWN2il614h+@aO<6lJ#gpr^8RwDJ*XmmdgQB z`<+V`aHDV|`bQ5-@`298FMfU*N(UN?#Zq$6h-$w0`3h1JjL~k;VUFg+D`}zhVom8fua#&@>ce8;R8(4iS{Sh*30(JljZrA|mP>VtiR{ERo7 z%a z*)*%Bv~4&rnp{^RArqnESueQ);yCh7uWlgO!q$}8iWp+2C!JNS7qN24rE82-kNHAf`k zAN(Jr-rs5>2wrxq&s^QBu5r|UiN;9JHWNs0YKE(Z8ENLo>|BQ_fOvY+cuCMZ>^MKt z_~cZB`S>%+70#$|M`KH|6_NB^EKt=a2Q$NVv&SPu*kK9f0czk`c&ym|E)CCgZtDVm zme+hf-&OQ)INMf1?KGtA=26`t$FwN`F3bT46{0X~o9hx+D{^Ln(Hru@4!;Ca4YX+b%A<*~q}ahx)G$M*vY3 z9RB55k5gIg6FJ7V$PGttm#K0(noUVhEtWV6aN1-2uOZW26Li;IZn1l0W|IDU;VL+{ z$T!^WZDR$Z&;pyFYG^9E5$sJ_pUfaD8YW1=Zw^yCXIqi5;d*w0Wn7@hG`&V;1+fCa z$=G&i1$P#9k3}2&73<5?Xbgf@m9vmPrMnzUX_o6idRfvDME|!L_0f6*wYA(bF?}2Z zK&S4Yh4z(%8y^sLe8ELudE2a@E}~QevcQYS+H>S;Cw@z?)hv(Tf9dvY)jNe5^0%lJ z=Ra>pptySrL$2@u-fWa_ko{o6gSPL>Pt5tdV^a+`Q*Kg++WY9SFP5EwYBZDkz^;C$ zwYfl2H;OB_?UWJztkxaMHZz~-GCg-PbSJFyNc27$_Df)aH62JF!gkS04Xjas&{J9S z5Q`dS7ck5)IUK#?DIQ=g$*;z{Y)J7}I97xfX4pzLzFj8cqO6e%ljh;+@$j&!JN6)(7i!B&D=$CQ*=kTg*}(;2FP0&=zc<;eoS#k>Z_0 z$gr8dJmZt28@8;_Z92!{1D#(yiac8MZts+JFLzJ^2&W6-m4rJ`K;&!^;*X=3ZUIq> zNb&M@4saflQ-Hm0nHZSW4j0iAbb@k@IUpQJnCoc(1+=pP&>lD|cKN~C9dWn!BS}|h zekQ++Eg#!_|C1E?Z^joS#Z0}6dE|ZJpB;%l6|o;Pb5k+y?Edwq-cSK;KFIB@z$Jv& zNA-k|mvndlYkxJ~fr7AX@C_3umSxRBqdmDhxLE!~%DXQtgwV*L9%0hG%9(SqD{@Y7 zhzB2Tn3CI$MZ68N7ERe7^J~mTT$2NQgxkIVL%!-~a}<#_qQNnjwF2yAx4+fXf*bV3>Z3G6>DD~h{UcH!C2 znnld9sw&F~Itt(pc|aPk05yY8_^*=olSYFwiovI$MmIi`!K3Ibjc<@bc-69u zwi%gQ7`;Ln(5=z&3H)I*mMKI00I9zY>i79icnIh_8b!b1T?N!>z~sGdYwTSOKbO65 zNA$V>Pc#7?g4wRd!Uc$A56vqASy&&mQGFG)aGnb*ui`9G+ul_J%}Sx@1*JP`Moh)#?XcEl1|UK*;KX88L}wB#`ExOJplh*TZROu4ke` z%`4o%$N!(S1ag3wIn@8L#D!5WIk}MsYD&cfo+byFXqnG(W_501?~4ii|GA`&Nr~2} zCUQ-eaV8!5QIa=rG5gY9BqhrEElc1xw%Y_UvJv?tqvBwTbfku^8(4x>3tIMgD(qh~ zQoYyJ@IaU~C&VdO0%<{PDO99!nNsF<4EQqfn=;{;*M1g{Mi`gRgzf!n%2i}L?*Q-r z#mJ1sYjGL?`U*vh`!YN(!#t3Vp8FxEN^0&J2IWI-FZAYp1~t>LUFQ(930ETx4hQ(fIg{;&uq~X9kuP|3|00rNf%RKM2#;T+qhfj= zqB~(#G5CG`vq~?jUQHK_V(BCEGzg)hcW&VlNV((HI*5%r^D~=n3d&V8L@DfY=rh0= z^FHUzSZ9O#c3%0t{Sd@*=ES(BB`}2@(HMw|#K%R!eA}Y>91qO8ERQD8Eh4cch{$W@ zUns=7VAtXUR0~M6X~98owj%m4Yka&PfhEo;5%QaVMrI(|NIvQtQcRgf=`bVWI@F%n zGUX%fh2=72qo!F;6eVqh)xnsuKoOE3p z!iVD`JU)#UvdKv@R}wJVD>i1WrKuMrnu)K-jsf#OyiiSwb=ICu~h8*O@UPdQtxJP7Y*33&UKfIu!Qa|fmy zx@d%TtK3^XA}Jjws4;RR7fLZG3TK83$ZWRU{juDTqxr4qKa~pGKCS;JF)ZJtsL5B8 zG!}>=kpu86o4_c;<0u$j`j2!_4I5#Y*{GOo8N`^Z&yF)|Rz~H9CY?07f$GxM_Ew|^ zOns3(y(IKlv_dJ}ufM!)n7sXpI@(o5;a-?I+`|LhAm>Hx@$Q<{mA z74N|BCD7ePK6=>y-?;9?*R;`~pGr!3yYphcz-Xd@dY6?i9sOWVbR{%+l{DYIV>=C# zRdWV$)wl8>Z{SD5Xm{SpCv}4;66ojHlrC2qcDE@kIY|vwpu<)8DJJr+7De|!d{5a{ zRnFnY1TRG|`6Jzeg2A4$>9{@_K0U8FTZUt_mN-=D zPKMc+cDI2H?;Vi&rzAly69w#j;*%eV#@P~42NOm3Uz|4nXX~x z3PaPh2EHK}kKuX7`TjGFD+wG<6NA>3gve5oIG*!fu2yYdW`lD0Oe~M)bnTgeUuTr! z%TXL0*rujG`VIx_l>hiPAYNQ(RCZr{6=<{9dgG)Cbnb4TYz76jtMlGo$(iF48d5hq zc`mmHBXXfsqFi<_xcWQeXCD@|NvRU;3)J$2`|D8B& zm7bv*V|apUqC3*NZ4VQ?haH5ru5$f8T||dgZZ`BNjtsE7XF)m#ZbFfp)P@Ajf!b%zkg_M3_8~wnPmTbO^ll3ec zy*KWJ3ElGI;~oGgVQaW0A~y|)yxoLDI1tGP$Gsa&p1HzDUf=4*ywzIGrcKN%7OZ6T z?94&E)7$G+g_?(5g4c6;(Fr*HD$4w;oH-x04DM}DWs6$jidC1vN8n?oxJw?3i3*LZ zly9h(9QlD$t7%Mpn5~8o;VbNiDnd@zAUVGeW$;D^0ZUl$+9B@`AT+@p*=ICDv*&$I z;|Dbt2%-S82TiRhC?>5zf7Nd83E-YW9niK8Y^0yf?#9fBGIHuuN~H=(rVR3lY-6*~ z$=1a*{=^cQDEshv-!63ODEM8#c6go^Ij+)I6hol9OT!~C=F6JM#=&t7Mw8seHVI%q zeb9Du4OX8TO52c927zoJ;~9D>>O<3mopRNFt~cFPk5X@iPla<5nn`F5$MT-A{D1q2 zl_l!T2ZLWka9Wds4*rql1t zz8J{egPc1rFB{P)y`5kPij{01jpllc8e)V4F?Pc&Ng7$IL z+LQN61`@S+Wu2eTCVtRx1Q=;FHl;52IFNHI@v>5R7bB?LYMA z`*J3*#)M*TVS6t41hJ4yQ#Iddx84tHoq^?Va%)kd_8+i0h2T#^mltA7}FDDEK)=yyV@s^KO64)niGrf3rr zwG-{j5dbDtFAs)0G5U*KmOS*1As%rcK-{j%=gD zk1T*{D7-Qg5TSm}!yeDtEM5Jb0bmIks0c8uL667bdcT1JVMVGNQK%!l7A_Z&eNlv3 zS_eKUC+^x@t-sv0YG@VMeB@7e#md3%{*M$Z4Rw9)&oIbm;VjC^QrNDB>lJ<^fPIm$ zH3p4V-HCu{_1lsMX4SDF6uFG!B@g6w0i(pjEp}p6QYPMnNu>S`O4zMqzF!#&aBYfU zHrmBhqjn-vBygj?-Tl;HIlh8r;GpcmZPH^mFX&VLjGEG&5PWW4i4;HUC8mEYw6v$9 z0>ow;6SqXNN*jGX*ySBDaa1ucceap{FW%q;`6V-4%Vn7S9b1aWC}PQOB`AA>7DM2a zB*k~2D;=f?vh?gk!b-d6jR)zsXAw~}tx^2Zj5e?eccPgGyA9y(M36cg8FqycKI9=m z6n~aG*6+3R^*<3qN?oVJrpfKR59UHfItgN0jT!9PU3{mcnB%osN^pXbU*~dhM3lwCL|uUQIL`A8V$P_$ru? zzuDjx3`aGYPSZN1sj~ZF6xqn`@2}~aEG{2Ab_(X$+77|Jxyv+P&(<>$ zPBoVk9pIzPE6TRYW8>>>at(Q2Sb(C>nC_9J%|N{ZlOmhg^qn}iY=?da9(2C$s#iR` zcf=PQ1Z3b(IKjJ-_TGE^gcJq9G7+?HN3b?2ZibO4I&mQk$ag+NeK%C2hl5ww5GR=9 zjB}LafyqEL7M=$48=<{-M-?}`3|%CA&4J(%5we!RYJ!L9+ZyWK$-zf9*V1;;U&UVB zD92PAlv$i?j{R+VG4kfXP*u&) zG@r6(YIJ0{u!($nW?KFpkz@UtftP&>W�!SnA}UrvUGE)Kdg#+0JmBbZjKLkY4K` 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 w0%VsPBq_udKS-ChR%yXM5LXd907v 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

WxW8PJoB_=nR3cyv)7PFqU!}#SJ_Iwun+S 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
p^H}98 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

0`_d*ig!(r{1M_i5v| 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%0DNGt|`q@k7dCQ4jKMNeE4aN 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

- + - diff --git a/misc/DimSim/package.json b/misc/DimSim/package.json index 8d8971109f..a5a99e8e1f 100644 --- a/misc/DimSim/package.json +++ b/misc/DimSim/package.json @@ -10,8 +10,8 @@ "preview": "vite preview" }, "dependencies": { - "@sparkjsdev/spark": "latest", "@dimforge/rapier3d-compat": "^0.14.0", + "@sparkjsdev/spark": "latest", "three": "^0.168.0" }, "devDependencies": { diff --git a/misc/DimSim/public/agent-model/dimsim_unitree_stub.glb b/misc/DimSim/public/embodiment/dimsim_unitree_stub.glb similarity index 100% rename from misc/DimSim/public/agent-model/dimsim_unitree_stub.glb rename to misc/DimSim/public/embodiment/dimsim_unitree_stub.glb diff --git a/misc/DimSim/scenes/dungeon/dungeon.glb b/misc/DimSim/scenes/dungeon/dungeon.glb deleted file mode 100644 index 5e08800983..0000000000 --- a/misc/DimSim/scenes/dungeon/dungeon.glb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cac0fc8c16d107e7ac4e69efde89c2cb6ef4bc66c34456a4dd0923218e5aafb1 -size 7990584 diff --git a/misc/DimSim/scenes/dungeon/index.js b/misc/DimSim/scenes/dungeon/index.js deleted file mode 100644 index da28af93f2..0000000000 --- a/misc/DimSim/scenes/dungeon/index.js +++ /dev/null @@ -1,54 +0,0 @@ -const SKY = { - topColor: '#1a1a22', - horizonColor:'#33323a', - bottomColor: '#0c0c11', - brightness: 0.4, - softness: 1.2, - sunStrength: 0.15, - sunHeight: 0.7, -}; - -export default async function build({ scene, THREE, physics, setSky, setEmbodiment, loadGLTF }) { - setSky(SKY); - - setEmbodiment({ - embodimentType: 'ground', - radius: 0.25, - halfHeight: 0.6, - lidarMountHeight: 1.1, - gravity: -9.81, - maxSpeed: 1.4, - turnRate: 2.5, - }); - - scene.add(new THREE.AmbientLight(0xffeedd, 0.4)); - const torch = new THREE.DirectionalLight(0xffd28a, 1.4); - torch.position.set(20, 30, 10); - torch.castShadow = true; - torch.shadow.mapSize.set(2048, 2048); - torch.shadow.camera.left = -40; - torch.shadow.camera.right = 40; - torch.shadow.camera.top = 40; - torch.shadow.camera.bottom = -40; - scene.add(torch); - const fill = new THREE.HemisphereLight(0x4a6fa5, 0x1a1418, 0.35); - scene.add(fill); - - console.log('[dungeon] loading dungeon.glb (~7.6 MB)'); - const gltf = await loadGLTF('./dungeon.glb'); - const dungeon = gltf.scene; - dungeon.traverse((o) => { - if (o.isMesh) { - o.castShadow = true; - o.receiveShadow = true; - } - }); - scene.add(dungeon); - physics.staticCollider(dungeon, 'trimesh'); - console.log('[dungeon] loaded'); - - return { - embodiment: null, - spawnPoint: { x: 0, y: 1.0, z: 0 }, - }; -} diff --git a/misc/DimSim/scenes/warehouse/index.js b/misc/DimSim/scenes/warehouse/index.js index 2f111cc26a..2083ab0d92 100644 --- a/misc/DimSim/scenes/warehouse/index.js +++ b/misc/DimSim/scenes/warehouse/index.js @@ -21,9 +21,19 @@ const SKY = { sunHeight: 0.6, }; -export default async function build({ scene, THREE, physics, setSky }) { +export default async function build({ scene, THREE, physics, setSky, setEmbodiment, loadGLTF }) { setSky(SKY); + setEmbodiment({ + embodimentType: 'ground', + radius: 0.3, + halfHeight: 0.85, + lidarMountHeight: 1.6, + gravity: -9.81, + maxSpeed: 1.4, + turnRate: 2.5, + }); + // ── Floor ──────────────────────────────────────────────────────────────── const floor = new THREE.Mesh( new THREE.BoxGeometry(FLOOR.width, 0.2, FLOOR.depth), @@ -152,6 +162,18 @@ export default async function build({ scene, THREE, physics, setSky }) { scene.add(ball); physics.dynamicCollider(ball, { shape: 'sphere', mass: 1.0, restitution: 0.6 }); + // ── Cross-scene GLB import test: pull the sectional out of apartment ───── + // The dedup'd GLB references textures via `../_textures/.png` which + // resolves relative to the GLB's URL — so loading it from any other scene + // still resolves correctly into /scenes/apartment/objects/_textures/. + const sectional = await loadGLTF('../apartment/objects/modern-l-shaped-sectional/state-default.glb'); + sectional.scene.traverse((o) => { + if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } + }); + sectional.scene.position.set(0, 0.5, -8); + scene.add(sectional.scene); + physics.staticCollider(sectional.scene, 'trimesh'); + // ── Lights ─────────────────────────────────────────────────────────────── scene.add(new THREE.HemisphereLight(0xc8d4e0, 0x303030, 0.45)); @@ -183,6 +205,6 @@ export default async function build({ scene, THREE, physics, setSky }) { return { embodiment: null, - spawnPoint: { x: 0, y: 0.5, z: -15 }, + spawnPoint: { x: 0, y: 1.0, z: -15 }, }; } diff --git a/misc/DimSim/scripts/extract_apt_to_js.py b/misc/DimSim/scripts/extract_apt_to_js.py deleted file mode 100644 index 55c0d161e5..0000000000 --- a/misc/DimSim/scripts/extract_apt_to_js.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python3 -""" -Extract apt.json → JS modules of apt-shape data (full parity with loadJson). - -Run from DimSim repo root: python3 scripts/extract_apt_to_js.py - -Reads: scenes/apartment/apt.json -Writes: scenes/apartment/textures/. - scenes/apartment/data/sky.js - scenes/apartment/data/tags.js - scenes/apartment/data/groups.js - scenes/apartment/data/lights.js - scenes/apartment/data/structure.js (top-level primitives) - scenes/apartment/data/objects.js (assets with full state lists) - -Then scenes/apartment/index.js assembles these + sceneApi.loadLevel() to feed -the engine's importLevelFromJSON — same code path as loadJson, so pickables, -multi-state objects, TV toggle etc. work exactly as before. - -Texture data URLs are extracted to disk and the materials are rewritten with -`texturePath: '.'`. loadLevel resolves these to absolute URLs -against the scene base before import. -""" - -import base64 -import hashlib -import json -from pathlib import Path -import re -import sys - -ROOT = Path(__file__).resolve().parents[1] -SCENE_DIR = ROOT / "scenes" / "apartment" -TEX_DIR = SCENE_DIR / "textures" -DATA_DIR = SCENE_DIR / "data" - - -def sniff_format(raw: bytes) -> str: - if raw[:4] == b"\x89PNG": - return "png" - if raw[:2] == b"\xff\xd8": - return "jpg" - if b"ftypavif" in raw[:32] or b"ftypmif1" in raw[:32]: - return "avif" - if b"ftypheic" in raw[:32] or b"ftypheix" in raw[:32]: - return "heic" - if raw[:6] in (b"GIF87a", b"GIF89a"): - return "gif" - if raw[:4] == b"RIFF" and raw[8:12] == b"WEBP": - return "webp" - return "bin" - - -def extract_textures(node): - """Walk every dict. Replace material.textureDataUrl (data: URL) with - material.texturePath (filename only). Write unique textures to disk.""" - if isinstance(node, dict): - mat = node.get("material") - if isinstance(mat, dict): - url = mat.get("textureDataUrl") - if isinstance(url, str): - m = re.match(r"^data:([^;]+);base64,(.+)$", url) - if m: - raw = base64.b64decode(m.group(2)) - h = hashlib.sha256(raw).hexdigest()[:12] - ext = sniff_format(raw) - name = f"{h}.{ext}" - path = TEX_DIR / name - if not path.exists(): - path.write_bytes(raw) - mat["texturePath"] = f"textures/{name}" - del mat["textureDataUrl"] - for v in node.values(): - extract_textures(v) - elif isinstance(node, list): - for v in node: - extract_textures(v) - - -# ── JS emit ────────────────────────────────────────────────────────────────── - - -def to_js_literal(obj, indent=0): - """Convert Python obj → JS object/array literal source. Booleans/null - map to JS spellings; everything else passes through json.dumps.""" - # json.dumps already produces valid JS for objects/arrays/strings/numbers. - # Booleans (true/false) and null are spelled the same in JSON and JS. - return json.dumps(obj, indent=2 if indent else None, separators=(", ", ": ")) - - -def emit_module(name: str, value, out_path: Path, note: str = "") -> None: - src = to_js_literal(value, indent=2) - header = f"// {note}\n" if note else "" - out_path.write_text(f"{header}export const {name} = {src};\n") - - -def emit_index(spawn=(2, 0.5, 3)) -> None: - content = f"""// scenes/apartment/index.js — entry for the apartment scene. -// -// JS-authored level data (apt-shape) is fed through importLevelFromJSON via -// scene-api.loadLevel(). That registers all assets in the engine's -// interaction registry, so E-key pickups, door states, and the TV toggle -// work exactly as they did when the scene loaded from apt.json directly. - -import {{ SKY }} from './data/sky.js'; -import {{ TAGS }} from './data/tags.js'; -import {{ GROUPS }} from './data/groups.js'; -import {{ LIGHTS }} from './data/lights.js'; -import {{ PRIMITIVES }} from './data/structure.js'; -import {{ ASSETS }} from './data/objects.js'; - -export default async function build({{ loadLevel }}) {{ - await loadLevel({{ - version: '2.0', - worldKey: 'default', - tags: TAGS, - primitives: PRIMITIVES, - assets: ASSETS, - lights: LIGHTS, - groups: GROUPS, - sceneSettings: {{ sky: SKY }}, - }}); - - return {{ - embodiment: null, - spawnPoint: {{ x: {spawn[0]}, y: {spawn[1]}, z: {spawn[2]} }}, - }}; -}} -""" - (SCENE_DIR / "index.js").write_text(content) - - -# ── Main ───────────────────────────────────────────────────────────────────── - - -def main(apt_path: str | None = None) -> None: - src = Path(apt_path) if apt_path else (SCENE_DIR / "apt.json") - if not src.exists(): - print(f"FATAL: {src} not found") - sys.exit(1) - - TEX_DIR.mkdir(parents=True, exist_ok=True) - DATA_DIR.mkdir(parents=True, exist_ok=True) - - print(f"Reading {src} ({src.stat().st_size / 1e6:.1f} MB)...") - doc = json.loads(src.read_text()) - - print("Extracting textures + rewriting material.textureDataUrl → texturePath...") - extract_textures(doc) - n_tex = sum(1 for _ in TEX_DIR.iterdir()) - print(f" {n_tex} files in textures/") - - sky = (doc.get("sceneSettings") or {}).get("sky", {}) - tags = doc.get("tags", []) - groups = doc.get("groups", []) - lights = doc.get("lights", []) - prims = doc.get("primitives", []) - assets = doc.get("assets", []) - - print("Emitting data/sky.js, data/tags.js, data/groups.js...") - emit_module("SKY", sky, DATA_DIR / "sky.js", note="Sky / atmosphere settings.") - emit_module("TAGS", tags, DATA_DIR / "tags.js", note="World tag list (string filter tags).") - emit_module("GROUPS", groups, DATA_DIR / "groups.js", note="Editor groupings.") - print(f"Emitting data/lights.js ({len(lights)} lights)...") - emit_module("LIGHTS", lights, DATA_DIR / "lights.js", note="Scene lights.") - print(f"Emitting data/structure.js ({len(prims)} top-level primitives)...") - emit_module( - "PRIMITIVES", - prims, - DATA_DIR / "structure.js", - note="Static geometry (walls, floor, ceiling, fixtures).", - ) - print(f"Emitting data/objects.js ({len(assets)} assets, incl. _deltaOnly)...") - emit_module( - "ASSETS", - assets, - DATA_DIR / "objects.js", - note="Placed assets with full state lists (interactive parity with apt.json).", - ) - - print("Emitting index.js...") - emit_index() - - print("\nDone. scenes/apartment/:") - for p in sorted(SCENE_DIR.iterdir()): - if p.is_file(): - print(f" {p.name:30s} {p.stat().st_size / 1024:8.1f} KB") - elif p.is_dir(): - sub = sorted(p.iterdir()) - tot = sum(f.stat().st_size for f in sub if f.is_file()) - print(f" {p.name}/ ({len(sub)} files, {tot / 1024:.1f} KB)") - - -if __name__ == "__main__": - main(sys.argv[1] if len(sys.argv) > 1 else None) diff --git a/misc/DimSim/scripts/profile-live.sh b/misc/DimSim/scripts/profile-live.sh deleted file mode 100755 index 8bb187c933..0000000000 --- a/misc/DimSim/scripts/profile-live.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -# Simple profiler: one-line-per-sample, appends to terminal (no clearing). -# Usage: bash scripts/profile-live.sh [interval_seconds] - -INTERVAL=${1:-3} - -printf "%-6s %8s %8s %8s %8s %8s %8s %8s %8s %s\n" \ - "TIME" "PY_CPU%" "PY_MB" "DENO_CPU%" "DENO_MB" "SIM_UI%" "SIM_UI_MB" "GPU_CPU%" "GPU_MB" "LOAD" -printf "%-6s %8s %8s %8s %8s %8s %8s %8s %8s %s\n" \ - "------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" "--------" - -while true; do - py_cpu=0; py_mem=0 - while IFS= read -r line; do - c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') - py_cpu=$(awk "BEGIN{print $py_cpu + $c}") - py_mem=$(awk "BEGIN{print $py_mem + $m}") - done < <(ps -eo %cpu,rss,command | grep -i '[p]ython.*dimos' 2>/dev/null) - - deno_cpu=0; deno_mem=0 - while IFS= read -r line; do - c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') - deno_cpu=$(awk "BEGIN{print $deno_cpu + $c}") - deno_mem=$(awk "BEGIN{print $deno_mem + $m}") - done < <(ps -eo %cpu,rss,command | grep -E '[d]eno|[d]imsim' 2>/dev/null | grep -v grep) - - # Find browser PIDs connected to bridge port 8090 (exclude deno server itself) - chrome_cpu=0; chrome_mem=0 - while IFS= read -r pid; do - [ -z "$pid" ] && continue - while IFS= read -r line; do - c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') - chrome_cpu=$(awk "BEGIN{print $chrome_cpu + $c}") - chrome_mem=$(awk "BEGIN{print $chrome_mem + $m}") - done < <(ps -p "$pid" -o %cpu,rss 2>/dev/null | tail -n +2) - done < <(lsof -i :8090 2>/dev/null | grep -v 'deno\|LISTEN' | awk 'NR>1{print $2}' | sort -u) - - # GPU processes — macOS Metal/WindowServer GPU usage - gpu_cpu=0; gpu_mem=0 - while IFS= read -r line; do - c=$(echo "$line" | awk '{print $1}'); m=$(echo "$line" | awk '{print $2}') - gpu_cpu=$(awk "BEGIN{print $gpu_cpu + $c}") - gpu_mem=$(awk "BEGIN{print $gpu_mem + $m}") - done < <(ps -eo %cpu,rss,command | grep -E '[W]indowServer|[G]PU.*Driver|com\.apple\.gpu' 2>/dev/null) - - py_mb=$(awk "BEGIN{printf \"%.0f\", $py_mem/1024}") - deno_mb=$(awk "BEGIN{printf \"%.0f\", $deno_mem/1024}") - chrome_mb=$(awk "BEGIN{printf \"%.0f\", $chrome_mem/1024}") - gpu_mb=$(awk "BEGIN{printf \"%.0f\", $gpu_mem/1024}") - load=$(sysctl -n vm.loadavg 2>/dev/null | awk '{print $2}' || echo "?") - - printf "%-6s %8.1f %6sMB %8.1f %6sMB %8.1f %6sMB %8.1f %6sMB %s\n" \ - "$(date '+%H:%M:%S')" "$py_cpu" "$py_mb" "$deno_cpu" "$deno_mb" "$chrome_cpu" "$chrome_mb" "$gpu_cpu" "$gpu_mb" "$load" - - sleep "$INTERVAL" -done diff --git a/misc/DimSim/scripts/speed-test.py b/misc/DimSim/scripts/speed-test.py deleted file mode 100644 index 88162ae845..0000000000 --- a/misc/DimSim/scripts/speed-test.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python3 -"""Speed test: send constant cmd_vel for N seconds, measure actual displacement via odom. - -Usage (from dimos venv): - python DimSim/scripts/speed-test.py [--speed 0.5] [--duration 5] - -Requires: dimos environment with LCM working. -Run while DimSim bridge is active (browser tab open). -""" - -import argparse -import math -import struct -import threading -import time - -# LCM multicast constants -LCM_ADDR = "239.255.76.67" -LCM_PORT = 7667 -MAGIC = 0x4C433032 - -# ── Minimal LCM encode/decode (no dimos import needed) ───────────────────── - - -def _encode_lcm_packet(channel: str, payload: bytes, seq: int) -> bytes: - ch_bytes = channel.encode("utf-8") - header = struct.pack(">II", MAGIC, seq) - return header + ch_bytes + b"\x00" + payload - - -def _decode_lcm_packet(data: bytes): - if len(data) < 8: - return None, None - magic, seq = struct.unpack(">II", data[:8]) - if magic != MAGIC: - return None, None - rest = data[8:] - null_idx = rest.index(0) - channel = rest[:null_idx].decode("utf-8") - payload = rest[null_idx + 1 :] - return channel, payload - - -# ── Twist encoding (geometry_msgs.Twist LCM) ─────────────────────────────── -# Layout: hash(8) + 6 x float64 (linear.x,y,z, angular.x,y,z) - - -def encode_twist( - linear_x=0.0, linear_y=0.0, linear_z=0.0, angular_x=0.0, angular_y=0.0, angular_z=0.0 -) -> bytes: - from dimos.msgs.geometry_msgs import Twist, Vector3 - - t = Twist( - linear=Vector3(x=linear_x, y=linear_y, z=linear_z), - angular=Vector3(x=angular_x, y=angular_y, z=angular_z), - ) - return t.lcm_encode() - - -# ── PoseStamped decoding ─────────────────────────────────────────────────── - - -def decode_pose_stamped(data: bytes): - """Decode geometry_msgs.PoseStamped using dimos LCM decoder.""" - from dimos.msgs.geometry_msgs import PoseStamped - - msg = PoseStamped.lcm_decode(data) - p = msg.position - q = msg.orientation - return p.x, p.y, p.z, q.x, q.y, q.z, q.w - - -# ── Main ──────────────────────────────────────────────────────────────────── - - -def main(): - import socket - - parser = argparse.ArgumentParser(description="DimSim speed test") - parser.add_argument("--speed", type=float, default=0.5, help="linear.x m/s (default 0.5)") - parser.add_argument("--duration", type=float, default=5.0, help="seconds to drive (default 5)") - args = parser.parse_args() - - speed = args.speed - duration = args.duration - - # ── Setup UDP multicast socket ── - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - sock.bind(("", LCM_PORT)) - - mreq = struct.pack("4s4s", socket.inet_aton(LCM_ADDR), socket.inet_aton("0.0.0.0")) - sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - sock.settimeout(0.1) - - # Send socket (separate so we can send without receiving our own) - send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) - - # ── Collect initial odom ── - print("Waiting for odom...") - odom_channel = "/odom#geometry_msgs.PoseStamped" - start_pose = None - - for _ in range(200): # up to 20s - try: - data, _ = sock.recvfrom(65535) - except TimeoutError: - continue - ch, payload = _decode_lcm_packet(data) - if ch == odom_channel: - start_pose = decode_pose_stamped(payload) - break - - if start_pose is None: - print("ERROR: No odom received. Is the bridge running?") - return - - sx, sy, sz = start_pose[0], start_pose[1], start_pose[2] - print(f"Start pose: ({sx:.3f}, {sy:.3f}, {sz:.3f})") - print(f"Sending cmd_vel: linear.x={speed} m/s for {duration}s") - print(f"Expected displacement: {speed * duration:.2f}m") - print() - - # ── Drive: send cmd_vel at 10 Hz, collect odom ── - seq = 1000 - stop = threading.Event() - poses = [] - - def send_cmd_vel(): - nonlocal seq - while not stop.is_set(): - twist_data = encode_twist(linear_x=speed) - packet = _encode_lcm_packet("/cmd_vel#geometry_msgs.Twist", twist_data, seq) - send_sock.sendto(packet, (LCM_ADDR, LCM_PORT)) - seq += 1 - time.sleep(0.1) # 10 Hz, matches planner rate - - sender = threading.Thread(target=send_cmd_vel, daemon=True) - sender.start() - - t0 = time.time() - while time.time() - t0 < duration: - try: - data, _ = sock.recvfrom(65535) - except TimeoutError: - continue - ch, payload = _decode_lcm_packet(data) - if ch == odom_channel: - pose = decode_pose_stamped(payload) - elapsed = time.time() - t0 - poses.append((elapsed, pose)) - - # Stop sending, send zero velocity - stop.set() - sender.join() - for _ in range(5): - twist_data = encode_twist() - packet = _encode_lcm_packet("/cmd_vel#geometry_msgs.Twist", twist_data, seq) - send_sock.sendto(packet, (LCM_ADDR, LCM_PORT)) - seq += 1 - time.sleep(0.05) - - # ── Results ── - if not poses: - print("ERROR: No odom received during test") - return - - last_pose = poses[-1][1] - ex, ey, ez = last_pose[0], last_pose[1], last_pose[2] - dx, dy, dz = ex - sx, ey - sy, ez - sz - dist = math.sqrt(dx * dx + dy * dy + dz * dz) - expected = speed * duration - ratio = dist / expected if expected > 0 else 0 - - print(f"{'─' * 50}") - print(f"End pose: ({ex:.3f}, {ey:.3f}, {ez:.3f})") - print(f"Displacement: {dist:.3f}m") - print(f"Expected: {expected:.3f}m") - print( - f"Ratio: {ratio:.2f}x {'(OK)' if 0.85 < ratio < 1.15 else '(SLOW!)' if ratio < 0.85 else '(FAST!)'}" - ) - print(f"Odom samples: {len(poses)} ({len(poses) / duration:.0f} Hz)") - print(f"{'─' * 50}") - - # Show velocity over time - print("\nVelocity trace (sampled):") - prev_t, prev_p = 0, start_pose - for i, (t, p) in enumerate(poses): - if i % max(1, len(poses) // 10) != 0: - continue - dt = t - prev_t - if dt > 0: - d = math.sqrt( - (p[0] - prev_p[0]) ** 2 + (p[1] - prev_p[1]) ** 2 + (p[2] - prev_p[2]) ** 2 - ) - v = d / dt - print(f" t={t:5.2f}s v={v:.3f} m/s pos=({p[0]:.3f}, {p[1]:.3f}, {p[2]:.3f})") - prev_t, prev_p = t, p - - sock.close() - send_sock.close() - - -if __name__ == "__main__": - main() diff --git a/misc/DimSim/src/bridge.ts b/misc/DimSim/src/bridge.ts index 0d411198dd..8c83f33a94 100644 --- a/misc/DimSim/src/bridge.ts +++ b/misc/DimSim/src/bridge.ts @@ -153,6 +153,7 @@ export class DimosBridge { console.log("[DimosBridge] control WS connected"); this._connected = true; this._startPublishing(); + this._flushPendingCommands(); }; this.wsControl.onmessage = (event: MessageEvent) => { @@ -525,14 +526,28 @@ export class DimosBridge { } } - /** Send a JSON command on the control WebSocket (used by EvalHarness). */ + _pendingCommands: Record[] = []; + + /** Send a JSON command on the control WebSocket. Queues if the socket + * isn't OPEN yet — _flushPendingCommands() drains on onopen. */ sendCommand(cmd: Record): void { const ws = this.wsControl; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(cmd)); + } else { + this._pendingCommands.push(cmd); } } + _flushPendingCommands(): void { + const ws = this.wsControl; + if (!ws || ws.readyState !== WebSocket.OPEN) return; + const pending = this._pendingCommands; + this._pendingCommands = []; + if (pending.length) console.log(`[DimosBridge] flushing ${pending.length} queued command(s)`); + for (const cmd of pending) ws.send(JSON.stringify(cmd)); + } + dispose(): void { this._stopPublishing(); if (this.wsControl) { this.wsControl.onclose = null; this.wsControl.close(); } diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index 4428219f07..708d72ce0e 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -3,14 +3,6 @@ import "./style.css"; import * as THREE from "three"; import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls.js"; import { AiAvatar } from "./AiAvatar.js"; -// Side-effect: assigning to a window global keeps Vite from tree-shaking -// the dynamic-import expression, so the IFC bundle is emitted as a chunk -// (dist/assets/dimsim-ifc.js) regardless of whether engine.js actually -// uses it. Scenes import via `@dimsim/ifc` through the importmap. -if (typeof window !== "undefined") { - /** @internal — escape hatch if a scene wants to load IFC programmatically. */ - window.__loadIfcLoader = () => import("./ifc-loader.ts"); -} import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js"; import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js"; @@ -5581,17 +5573,19 @@ function despawnEphemeralAgents(reason = "task-end") { for (const a of doomed) removeAiAgent(a, reason); } -function createAiAgent({ ephemeral = false, avatarUrl } = {}) { +function createAiAgent({ ephemeral = false, avatarUrl, radius, halfHeight } = {}) { const id = `agent-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`; const _avatarUrl = avatarUrl !== undefined ? avatarUrl - : ["/agent-model/dimsim_unitree_stub.glb"]; + : ["/embodiment/dimsim_unitree_stub.glb"]; const agent = new AiAvatar({ id, scene, rapierWorld, RAPIER, avatarUrl: _avatarUrl, + radius, + halfHeight, // Headless mode in dimos: skip visual rendering, keep colliders for physics headless: false, }); @@ -6187,9 +6181,14 @@ function _findSceneFloorY(x = 0, z = 0) { 0, fromY + 500, ); + // Pure-Three.js scenes (e.g. apartment loading structure.glb directly via + // loadGLTF + scene.add) put their floor outside of assetsGroup/primitivesGroup. + // Ray-cast the full scene as a third target — the worldN.y >= 0.5 filter + // below still rules out walls, ceilings, and the skybox. const hits = [ ...raycaster.intersectObject(assetsGroup, true), ...raycaster.intersectObject(primitivesGroup, true), + ...raycaster.intersectObject(scene, true), ]; let floorY = null; for (const h of hits) { @@ -7142,7 +7141,13 @@ if (dimosMode) { // default avatarUrl (the dimsim_unitree_stub.glb) take over. The legacy // "hide the group when no embodiment" path predates dimos integration and // is no longer reached here. - const agent = createAiAgent({ ephemeral: false }); + const pendingEmb = sceneApi._getPendingEmbodiment?.(); + const agent = createAiAgent({ + ephemeral: false, + avatarUrl: pendingEmb?.avatarUrl, + radius: pendingEmb?.radius, + halfHeight: pendingEmb?.halfHeight, + }); aiAgents.push(agent); sceneApi._setAgent(agent); const spawnPos = sceneCfg.spawnPoint || { x: 2, y: 0.5, z: 3 }; diff --git a/misc/DimSim/src/sceneApi.ts b/misc/DimSim/src/sceneApi.ts index bba039d413..92e21799c7 100644 --- a/misc/DimSim/src/sceneApi.ts +++ b/misc/DimSim/src/sceneApi.ts @@ -99,10 +99,10 @@ export function setSky(opts: Record): void { * the GLB and re-asserts visibility immediately. * * Typical configs: - * setEmbodiment({ embodimentType: 'ground', avatarUrl: '/agent-model/dimsim_unitree_stub.glb', + * setEmbodiment({ embodimentType: 'ground', avatarUrl: '/embodiment/dimsim_unitree_stub.glb', * radius: 0.18, halfHeight: 0.25, maxSpeed: 1.5, turnRate: 2.5 }); * - * setEmbodiment({ embodimentType: 'drone', avatarUrl: '/agent-model/drone.glb', + * setEmbodiment({ embodimentType: 'drone', avatarUrl: '/embodiment/drone.glb', * radius: 0.3, halfHeight: 0.1, gravity: 0, maxSpeed: 3.0 }); * * All fields are forwarded to the bridge's EmbodimentConfig (see @@ -111,17 +111,21 @@ export function setSky(opts: Record): void { */ let _pendingEmbodiment: Record | null = null; +export function _getPendingEmbodiment(): Record | null { + return _pendingEmbodiment; +} + export function setEmbodiment(config: Record): void { if (!_sendPhysics) throw new Error("scene-api not initialized"); const w = window as any; if (w.__dimosBridge) { + console.log("[sceneApi] setEmbodiment applying:", config.embodimentType, "gravity=" + config.gravity); if (w.__dimosBridge._handleEmbodimentConfig) { w.__dimosBridge._handleEmbodimentConfig(config); } _sendPhysics({ type: "embodimentConfig", ...config }); } else { - // Scene build() runs before engine.js wires up window.__dimosBridge. - // Queue and flush from engine.js once the bridge is constructed. + console.log("[sceneApi] setEmbodiment queued (bridge not ready):", config.embodimentType); _pendingEmbodiment = config; } } diff --git a/misc/DimSim/src/style.css b/misc/DimSim/src/style.css index dd9c1c0a4f..ec258b99a1 100644 --- a/misc/DimSim/src/style.css +++ b/misc/DimSim/src/style.css @@ -1048,11 +1048,14 @@ html[data-mode="sim"] .side-panel { padding: 1px 5px; } -/* Dimos-only: hide the sim side panel and command bar — leave only - top-left status + bottom-left WASD shortcuts visible. */ +/* Dimos-only: hide the sim side panel + command bar + top-left status + pill (the small "Click to look around" + B/G chip is the human-player + prompt; the agent is driven externally). Bottom-left WASD shortcuts + stay visible. */ body.dimos-mode #agent-panel, body.dimos-mode #sim-panel-open, -body.dimos-mode #agent-command-bar { +body.dimos-mode #agent-command-bar, +body.dimos-mode .status-floating { display: none !important; } From 69f6c06b73830e51679735e6227cdf92924c82f9 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Wed, 20 May 2026 14:05:30 -0700 Subject: [PATCH 37/43] apartment: re-embed textures into each GLB (drop _textures/ shared dir) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each GLB is now self-contained — no relative `../_textures/` URIs, fully portable as a drag-and-drop unit. Trade-off: +85 MB on disk because the wood PNG (used by 10 different library assets) is now duplicated 10x instead of shared. Cost is LFS-backed so repo clone overhead is minimal. Library-asset dedup still in effect: 4 dining chairs still share one GLB file, etc. Only cross-library texture sharing was reversed. --- .../scenes/apartment/objects/_textures/029fc2889f15b8db.png | 3 --- .../scenes/apartment/objects/_textures/04c871f0ab1c5a24.png | 3 --- .../scenes/apartment/objects/_textures/0b7865c5ee29745f.png | 3 --- .../scenes/apartment/objects/_textures/0d2c31fb2341e538.png | 3 --- .../scenes/apartment/objects/_textures/0de92d4a4960538f.png | 3 --- .../scenes/apartment/objects/_textures/149df7451fd5cd20.png | 3 --- .../scenes/apartment/objects/_textures/18312adcf28b40cb.png | 3 --- .../scenes/apartment/objects/_textures/1b1bd30153ae132d.png | 3 --- .../scenes/apartment/objects/_textures/1dd8eb95b9656b26.png | 3 --- .../scenes/apartment/objects/_textures/2243b7f1057033b2.png | 3 --- .../scenes/apartment/objects/_textures/226283fd842aafd4.png | 3 --- .../scenes/apartment/objects/_textures/31e13b5ed8a635c6.png | 3 --- .../scenes/apartment/objects/_textures/39ae632dc93d53d5.png | 3 --- .../scenes/apartment/objects/_textures/4a473b376851a332.png | 3 --- .../scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png | 3 --- .../scenes/apartment/objects/_textures/4fe42c4e88745307.png | 3 --- .../scenes/apartment/objects/_textures/516aef161ed815e2.png | 3 --- .../scenes/apartment/objects/_textures/593837db8223179b.png | 3 --- .../scenes/apartment/objects/_textures/5a0cf43d6539c265.png | 3 --- .../scenes/apartment/objects/_textures/5c01ca65028f6506.png | 3 --- .../scenes/apartment/objects/_textures/6a6d187153f2d634.png | 3 --- .../scenes/apartment/objects/_textures/748f081db9a0babd.png | 3 --- .../scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png | 3 --- .../scenes/apartment/objects/_textures/77a18d10c12dcfb6.png | 3 --- .../scenes/apartment/objects/_textures/7a60c10294c2d91c.png | 3 --- .../scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png | 3 --- .../scenes/apartment/objects/_textures/9286d87f35fbff9d.png | 3 --- .../scenes/apartment/objects/_textures/936d912a9d979920.png | 3 --- .../scenes/apartment/objects/_textures/93d78e540cf2241a.png | 3 --- .../scenes/apartment/objects/_textures/99621b614efba8ba.png | 3 --- .../scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png | 3 --- .../scenes/apartment/objects/_textures/9edae70daab1b822.png | 3 --- .../scenes/apartment/objects/_textures/a35e1f2bca4505ec.png | 3 --- .../scenes/apartment/objects/_textures/a84b378c04f0f97f.png | 3 --- .../scenes/apartment/objects/_textures/b53cdda7af4ed347.png | 3 --- .../scenes/apartment/objects/_textures/b8800311c4074101.png | 3 --- .../scenes/apartment/objects/_textures/c297f1edeca7326c.png | 3 --- .../scenes/apartment/objects/_textures/ca8232ed9640b9f6.png | 3 --- .../scenes/apartment/objects/_textures/d2d755c8d03e5e91.png | 3 --- .../scenes/apartment/objects/_textures/d6b5c937576cb0d5.png | 3 --- .../scenes/apartment/objects/_textures/e0be276a3c9143e0.png | 3 --- .../scenes/apartment/objects/_textures/e4ca1adbdeae661e.png | 3 --- .../scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png | 3 --- .../scenes/apartment/objects/_textures/eaa5df17ce4675b1.png | 3 --- .../scenes/apartment/objects/_textures/fda39aeccd530d8e.png | 3 --- .../scenes/apartment/objects/arc-floor-lamp/state-default.glb | 4 ++-- .../apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb | 4 ++-- .../objects/assorted-decorative-plants/state-default.glb | 4 ++-- .../scenes/apartment/objects/bedside-table/state-default.glb | 4 ++-- .../apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb | 4 ++-- .../objects/bohemian-style-woven-macrame/state-default.glb | 4 ++-- .../apartment/objects/bookshelf-decor/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../apartment/objects/console-with-cabinets/state-default.glb | 4 ++-- .../objects/console-with-cabinets/state-mlsncwfv-pq43.glb | 4 ++-- .../state-default.glb | 4 ++-- .../scenes/apartment/objects/dining-chair/state-default.glb | 4 ++-- .../objects/framed-abstract-oil-painting/state-default.glb | 4 ++-- .../objects/framed-landscape-oil-painting/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../apartment/objects/hardcover-books-1/state-default.glb | 4 ++-- .../apartment/objects/hardcover-books/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../scenes/apartment/objects/laptop-open/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../large-ornate-mahogany-dining-table/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../scenes/apartment/objects/large-wardrobe/state-default.glb | 4 ++-- .../apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../objects/modern-l-shaped-sectional/state-default.glb | 4 ++-- .../objects/modern-office-work-desk/state-default.glb | 4 ++-- .../objects/modern-tripod-floor-lamp/state-default.glb | 4 ++-- .../objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../scenes/apartment/objects/queen-size-bed/state-default.glb | 4 ++-- .../objects/salt-and-pepper-shakers/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../apartment/objects/small-potted-plant/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../apartment/objects/television/state-mlr4dvjl-fyw3.glb | 4 ++-- .../state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- .../scenes/apartment/objects/vintage-poster/state-default.glb | 4 ++-- .../apartment/objects/wooden-bookcase/state-default.glb | 4 ++-- .../wooden-coffee-table-with-glass-top/state-default.glb | 4 ++-- .../apartment/objects/work-chair/state-mlrqdudw-h1ax.glb | 4 ++-- .../apartment/objects/woven-rattan-placemat/state-default.glb | 4 ++-- .../state-default.glb | 4 ++-- 96 files changed, 102 insertions(+), 237 deletions(-) delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png delete mode 100644 misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png diff --git a/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png b/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png deleted file mode 100644 index bb43e9ccca..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/029fc2889f15b8db.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:029fc2889f15b8db2bda323f955efc8c40299209c4018c2706ee63c24412981d -size 7132320 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png b/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png deleted file mode 100644 index 063dd24ab8..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/04c871f0ab1c5a24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04c871f0ab1c5a24ff62e6e927cfa36fb4e48cf5a720708aa19ceff936c6de79 -size 277456 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png b/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png deleted file mode 100644 index 1c025a3c83..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/0b7865c5ee29745f.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b7865c5ee29745f9bc38182b00b220e88cdf68950422dc664893577cd92689d -size 282900 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png b/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png deleted file mode 100644 index 3c74cbd66b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/0d2c31fb2341e538.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d2c31fb2341e538f3b9c727de3bb412c7a5a634cae1d32f8cb673ebcd06bea8 -size 265156 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png b/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png deleted file mode 100644 index 7351584764..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/0de92d4a4960538f.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0de92d4a4960538f2fb94cc3c8e4886292be1c504c70b6209ce2296bc9889e6e -size 277208 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png b/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png deleted file mode 100644 index f6169ca91e..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/149df7451fd5cd20.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:149df7451fd5cd2088c197c3ca5456d8865e594508570f01066a3a190638c3a9 -size 275116 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png b/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png deleted file mode 100644 index 29381b137f..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/18312adcf28b40cb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18312adcf28b40cb1e0919cf965597714defbce18539d094d614a7e3c89860d4 -size 271888 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png b/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png deleted file mode 100644 index 503875b58a..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/1b1bd30153ae132d.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b1bd30153ae132d97c1e0db32c4ab5cf8201aeab0195107288c5124928c2a64 -size 275896 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png b/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png deleted file mode 100644 index 3f4266d0b4..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/1dd8eb95b9656b26.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dd8eb95b9656b266c6a967afde328b4128764176ab8affe81b315166367a44e -size 265588 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png b/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png deleted file mode 100644 index ae8fc50a07..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/2243b7f1057033b2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2243b7f1057033b22c45d825c943dd9c30226c9badef03f9ff29866db7d688fd -size 277500 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png b/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png deleted file mode 100644 index 55720fe416..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/226283fd842aafd4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:226283fd842aafd454396beb374c46a474da7707d1863b57d042445e26a599a4 -size 1421144 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png b/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png deleted file mode 100644 index 1ccad1c8c1..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/31e13b5ed8a635c6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:31e13b5ed8a635c67d83e5307d2b0f06adc947be4e0b27fac0da2f8610f22251 -size 273776 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png b/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png deleted file mode 100644 index 0137f92aa9..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/39ae632dc93d53d5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39ae632dc93d53d599e800dded83e6b6a0cf5f7e7dcd69572651b28a5a7b7bb9 -size 265304 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png b/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png deleted file mode 100644 index 1a38e5fdf9..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/4a473b376851a332.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a473b376851a332a70de287dd6ddb84106c16cf675c178ed90657befdf05378 -size 647932 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png b/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png deleted file mode 100644 index e82de2f2c5..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/4cefc6fce1c0c9f9.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4cefc6fce1c0c9f904afd1cd747ed96abc7035c3a7d76118cfd3d64cae1f969b -size 592716 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png b/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png deleted file mode 100644 index 1081953c3b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/4fe42c4e88745307.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fe42c4e887453078288439f61d893d73c29f13ce424346ba2683c51d9ff3c1a -size 273360 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png b/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png deleted file mode 100644 index 56a045da34..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/516aef161ed815e2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:516aef161ed815e22f25d0f6dcebb9c98ca03383e64bbddaa2930bc6775367f9 -size 528036 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png b/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png deleted file mode 100644 index de932b55ce..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/593837db8223179b.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:593837db8223179ba54ad25e90c4f3edc0c25be2332cb2e742fdef95a41a8037 -size 731636 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png b/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png deleted file mode 100644 index 400dd316bc..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/5a0cf43d6539c265.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a0cf43d6539c26533d489f6854c9f9218a3668b7794ad3413a03fe6e738ce28 -size 271420 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png b/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png deleted file mode 100644 index 101e5e02c3..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/5c01ca65028f6506.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c01ca65028f6506e95898b7a1d0521ab4223c70b3694a1d62bc3380884955dd -size 275832 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png b/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png deleted file mode 100644 index b23d6a7a9f..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/6a6d187153f2d634.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a6d187153f2d634159278dcfdce55eeb2d755a719eea694cadd5beac063c577 -size 1296848 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png b/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png deleted file mode 100644 index 9324122ed5..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/748f081db9a0babd.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:748f081db9a0babd8502fe9fb8903f488d01b4a9b32710df7e95bb71970ba008 -size 286084 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png b/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png deleted file mode 100644 index 31c188b3bf..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/762ee1c4a1a1db1d.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:762ee1c4a1a1db1d3501dad221f71cdeaf08d90dfda8b1649091946e5bfdeb54 -size 281168 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png b/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png deleted file mode 100644 index 12d9fb52ab..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/77a18d10c12dcfb6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77a18d10c12dcfb686c14f8cd33b30ba90628d3b3c394218b94b7524c0d8a4d2 -size 269220 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png b/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png deleted file mode 100644 index 1a2e7bbb5b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/7a60c10294c2d91c.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a60c10294c2d91c582847c11f381435501298b110dc91e4f855ab4f713733d4 -size 277332 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png b/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png deleted file mode 100644 index 5e7992f158..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/86f7b7c1dee6ac44.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86f7b7c1dee6ac44532a98cea9b0d8640d0aa0fa7ac64b9d35f9ec7ca3c65301 -size 274048 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png b/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png deleted file mode 100644 index d3ac676be3..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/9286d87f35fbff9d.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9286d87f35fbff9d64f1c79f27800642358ecabaf3def562bc804dd5bf93cf01 -size 183576 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png b/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png deleted file mode 100644 index 0712f3cb5b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/936d912a9d979920.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:936d912a9d9799201c1dba430146118c43c9675ac44f304bf7765d23c57a721a -size 264184 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png b/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png deleted file mode 100644 index b663744548..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/93d78e540cf2241a.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93d78e540cf2241a2f8e17e46cbee336fc2798a6f6900aa041c14ca32727c5e2 -size 264376 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png b/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png deleted file mode 100644 index c2381f4b6e..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/99621b614efba8ba.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99621b614efba8ba9be34da7448b527d523d38ba7dd3ef36014d66f8aa70d6e4 -size 276804 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png b/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png deleted file mode 100644 index 8f9dc3b917..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/9cbdb6a74476b4c7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9cbdb6a74476b4c795fe67c8c3d7521985e602b1c60a753c3eee3351a4364ad7 -size 277920 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png b/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png deleted file mode 100644 index 02b7df1aff..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/9edae70daab1b822.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9edae70daab1b822f48e13c20df4740d31078d056aad185abdc14b63efd74d87 -size 270672 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png b/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png deleted file mode 100644 index 351c7c4887..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/a35e1f2bca4505ec.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a35e1f2bca4505ecfda98a61a39f2486c650b27735a66725f28e0bd466358417 -size 267628 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png b/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png deleted file mode 100644 index 737be207bf..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/a84b378c04f0f97f.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a84b378c04f0f97f35f8012f6426543e8326dd6441c298f6c021cbd4669a99a5 -size 270912 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png b/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png deleted file mode 100644 index 3bca1ba68f..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/b53cdda7af4ed347.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b53cdda7af4ed34799bd2efaa1c5d179919124285f37d3ad2beb71b63b5e49c8 -size 519160 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png b/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png deleted file mode 100644 index 1098bfa998..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/b8800311c4074101.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8800311c40741013d4a9058798e46b77a52fea92d5c61435a991f79567f50fc -size 28358184 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png b/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png deleted file mode 100644 index 0b70f9766c..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/c297f1edeca7326c.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c297f1edeca7326c7408ae1f8147f9aac236a8f50b6fd3e7914d1c12ea45fea4 -size 282340 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png b/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png deleted file mode 100644 index cf187f20e2..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/ca8232ed9640b9f6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca8232ed9640b9f679fff1d0b339e4fbd60532f3b3f6bc056429b0dcd102ceaa -size 135752 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png b/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png deleted file mode 100644 index 87b2bb5919..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/d2d755c8d03e5e91.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2d755c8d03e5e919ec686510d7ad265dc9aa9574f5602cd8258b7f41900f387 -size 268996 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png b/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png deleted file mode 100644 index c39d8de37b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/d6b5c937576cb0d5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6b5c937576cb0d5462662c5671c366435391fea85945ba4d935e1592b15fcd0 -size 1429032 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png b/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png deleted file mode 100644 index 9f5c457140..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/e0be276a3c9143e0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0be276a3c9143e049bc778a0ba21d2cee688dc49498ca29b1aa7135f56fae2c -size 263664 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png b/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png deleted file mode 100644 index c6a49fd9eb..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/e4ca1adbdeae661e.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4ca1adbdeae661e94a0063f2702ddd06464338c79bc9fbc5badcb11dd3efea1 -size 264648 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png b/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png deleted file mode 100644 index ca1f62180b..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/e7e2c3b1b49be3f4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7e2c3b1b49be3f4aa86d8f7f3c596f8497718fbdedc8038a5260fdfbe287ba8 -size 279252 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png b/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png deleted file mode 100644 index 6e0502b624..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/eaa5df17ce4675b1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eaa5df17ce4675b1823915e7327565bb1a6d36695bbfdfa0b2296f8aa1a61ba1 -size 283264 diff --git a/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png b/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png deleted file mode 100644 index a169b885af..0000000000 --- a/misc/DimSim/scenes/apartment/objects/_textures/fda39aeccd530d8e.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fda39aeccd530d8ec2915c2eba00959d3f748b712a4b662330aec552e03f82bc -size 281796 diff --git a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb index 71c087e61d..ddede4e302 100644 --- a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4402409cbbba6ad045503b78cd13db570dfb74e44d6bd9cdbcb4dad6d62f8dd -size 66780 +oid sha256:c4ef00b2304b405746f09f6bd6b35c9981937366a70d64f087a19d9f9b17a5d2 +size 585968 diff --git a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb index 71c087e61d..ddede4e302 100644 --- a/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb +++ b/misc/DimSim/scenes/apartment/objects/arc-floor-lamp/state-mlskixi4-xebf.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4402409cbbba6ad045503b78cd13db570dfb74e44d6bd9cdbcb4dad6d62f8dd -size 66780 +oid sha256:c4ef00b2304b405746f09f6bd6b35c9981937366a70d64f087a19d9f9b17a5d2 +size 585968 diff --git a/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb b/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb index 5bd6a69afb..adfaa8c6dd 100644 --- a/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/assorted-decorative-plants/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b72faccf46d202bcb8d159c55e3621dd97052fd3562a0ecc83c2ed92e70ceea -size 71752 +oid sha256:68ea9331c2464dcb36f042e670bd7f1c3b8125ff11b4c50cae133871691327e9 +size 906376 diff --git a/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb b/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb index 3a9c1c1fe0..de7a19af09 100644 --- a/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/bedside-table/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a05fbcd9449cadd8fbdf1d93adf65956030f29282e15fdf3b5743951110c3b8 -size 12292 +oid sha256:ec847525d25bb8e2391c0b2eb4edf29c3a3e123af71cc1f52fd9993be0f3f31f +size 7144640 diff --git a/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb b/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb index ed9dd1ac64..0c6dc2bed4 100644 --- a/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb +++ b/misc/DimSim/scenes/apartment/objects/bedside-table/state-mlsm2hfh-kpff.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6948f24b677fb73e457bc19ded3274c00f678faa9bbfd370902b1d60c8a4f26 -size 19612 +oid sha256:220267491822f796fe71cfd731c48d501428fb57389758f9423f65c0bf08d160 +size 7151960 diff --git a/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb b/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb index 2b255c03d9..bf05242218 100644 --- a/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/bohemian-style-woven-macrame/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9117229ea2c6e232eeb9fdb2d058d220f82ba0a6160f249ece4e090fc2755ef9 -size 31328 +oid sha256:84be0236520a9fa691211c7a7e50c84b33429adf046d5329f49c5f6aaee23b67 +size 313152 diff --git a/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb b/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb index 9bd0735229..7581089def 100644 --- a/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/bookshelf-decor/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bc5bf9736afa07fcca43169bd2d8c6092ae2296471773138ced660dc0ac7c08 -size 65364 +oid sha256:762b4c306db409a42ddda0e771d94f5876686529a3e16111f1346d1d5666ed33 +size 1082604 diff --git a/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb b/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb index 024a32037e..3f8f86822d 100644 --- a/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/chunky-knit-wool-throw-blanket-with-visible-weav/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:768df13243a11ff2201e37f9b5a99302662dcd3c90a671af64b9fb24eaf257f2 -size 194848 +oid sha256:4658bd699a5c33748530135b3cf64006373143400fac2a452e72d94f1de84b33 +size 463872 diff --git a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb index 373f3d8cb2..54fae734ca 100644 --- a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46db8f9c917f49673f3934bfd70a9ec6d88a8f114098113e2357fc5f829b8dfb -size 165468 +oid sha256:f0a0fa0fff9eb80fc2c5d1b2c8a61be3f84dd24cdbc691831da13faee3712e46 +size 7297816 diff --git a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb index 7cb0096127..1fd4c259a4 100644 --- a/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb +++ b/misc/DimSim/scenes/apartment/objects/console-with-cabinets/state-mlsncwfv-pq43.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:823a130962bde1257cb5aeb69e9375ff407ca6c3548fd20578a2bbd957cc37fd -size 168048 +oid sha256:5894a964cec58a60cfb89deaa12426816271d8aef1e73208f6b52df81d4d9a80 +size 7300400 diff --git a/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb b/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb index 1d4499ec8e..52a3183344 100644 --- a/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/detailed-porcelain-dinner-plate-with-gold-rim-an/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa831f58667d6a31a4b7e276e83354f4649731f2e7f8bf2646d1f28ddb373e3e -size 54756 +oid sha256:e2981caa6d6c2ad8ceb54b7a625103065a16d5a9cbfec4b1eb59eb9d04b994bd +size 328560 diff --git a/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb b/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb index 9fcb23b239..6470be2f51 100644 --- a/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/dining-chair/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98fa7ff02e8c6086ca703fd6b499c4a01c04fbe32516f71c7ea0a72659b80b67 -size 519420 +oid sha256:332fd1cf6db3b245f2fa29d3d853a0afb8a007826e63a56e4500829a6b25e816 +size 8170956 diff --git a/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb b/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb index 093cfc57ba..cfeb6ebe5b 100644 --- a/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/framed-abstract-oil-painting/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76197c6a01fc4867613b3053040f19929edd300262b77d7f101313a4b03da120 -size 5324 +oid sha256:c03169172ec1d9384b911cf3ebf7c289af0b0e3038062ca0440e098b4463fc6c +size 1426496 diff --git a/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb b/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb index fa618f5251..5aa644ddeb 100644 --- a/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/framed-landscape-oil-painting/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2ac86c1ace17f446cbea370201657f5e01c38909850785765026336baa3dbe5 -size 97952 +oid sha256:becc877432db5a547ad6875b2d996203d83a2fb58b4ec0c6bbdd0b93bde8e65d +size 7961960 diff --git a/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb b/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb index 1649af14be..fa9b213a93 100644 --- a/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/galvanized-steel-watering-can-with-a-long-spout/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd66639909e00c5ad6dc1895f83ba5e397a0f5ff2bf1d8ec3b80d91334dd64ad -size 69856 +oid sha256:dd5c886eb576b8d06b330558e7f4f1d9ccee7f1f8b93cae4aee477b230a9ac38 +size 340556 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb index a341f0fa38..d531ec01e0 100644 --- a/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/hardcover-book-lying-open-with-paper-pages-and-p/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49561d71d4fbe2a9f24c4d42686c566ce044b672450bf1f5de4e0571f4dcfe85 -size 15184 +oid sha256:d3c3ff196eb5b512e22dd42951f05c9d1de304dab8842a67f5fe87e1fec5ea0d +size 290324 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb index f94c9574e5..0e72ededc7 100644 --- a/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/hardcover-books-1/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc2d86d6406df1a814c6921094ad0dda206de1c5374791dfad3a778f6f6c802f -size 474816 +oid sha256:809728c182a237caf91b109b77f82c0475c74f31dcb35925feaceb830c58dbe9 +size 743840 diff --git a/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb b/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb index b880df573d..54d130472c 100644 --- a/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/hardcover-books/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25d80266ed16e634b364db9a2154a8aece682d7ff66df2caa6eff1df1895048e -size 291272 +oid sha256:1f1324e5e42bb08edf674d1e0451c231bec5ab531528f481bcae291f628c8a9c +size 562212 diff --git a/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb b/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb index d79179ed9a..5ae7065c52 100644 --- a/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/high-speed-kitchen-blender-with-glass-jar-and-me/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da28fb4e304f4989090713118899f3fb593159b57704f19e514f6dc308c4a67a -size 158272 +oid sha256:3b8a948b157b1c561c6cedd96a007030a2d619b92a5b0f3ebf9f65ddbb481bf4 +size 434196 diff --git a/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb b/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb index f199a94bb3..5787b0980c 100644 --- a/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/laptop-open/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5653f104f577443d6a7e5cf42598f636011c17dcbdc73bde86cff0a1b7d8e43 -size 289124 +oid sha256:72c2c85e8b32fb9fa91c4ca28b4424ae21ac46fb6b3989c65ccc5e0948cd04c1 +size 840568 diff --git a/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb index a5a46a5dd6..26cbae5a6d 100644 --- a/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/large-detailed-oak-tree-with-textured-bark-and-l/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ac283bb3f3ea945ae02f59c8046a524bf5513f1f245ca338d3c29e79bfec6f3 -size 130080 +oid sha256:9201662dead3d7925f6ef0d1fe383842233590e8d5c36f735ffea0ba4b390d0d +size 29917356 diff --git a/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb index 1d368d2416..c8f5cfdf9f 100644 --- a/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/large-ornate-mahogany-dining-table/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:211e118a515bcc34dfdcc4976335e0885cb11c798a50fdb37c34c7b74db0181c -size 249528 +oid sha256:7ad521d7b40fe9dd786256da2ef132c879af08d2223c71d41830b590aaf264af +size 7381876 diff --git a/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb index 1b10a0d35c..ff744b7cc1 100644 --- a/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/large-terracotta-garden-pots-with-intricate-flor/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:141749af291af410c057d56bd8286a8439d69dc91bdd889fe71ac6ab95735287 -size 51604 +oid sha256:fada31452d0b3d5c833df43ba78345d132e5df4b082a50f0d75440ea104f6b0e +size 316788 diff --git a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb index e32b7e317b..33ae45f5b6 100644 --- a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7ab1d459f6fb683f6adec3660eee11af5ef19aefb2cfc9ebd06638be952ab0d -size 40948 +oid sha256:c20a8a18501baaf1a0cd8f6061eb5505ef1cb149a4fa37d522dd43eb4f0462a7 +size 7173296 diff --git a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb index d5c6d78782..0fc5826004 100644 --- a/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb +++ b/misc/DimSim/scenes/apartment/objects/large-wardrobe/state-mlsmklqi-mykf.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd4ca6c219c5f4ee924f09ca6beeb4df857d98507e5abfd9b5d73128f1c4bacf -size 56920 +oid sha256:eca7d7650015f635e34cc38e692088dc5724372f7fda3cf98a3b0d857a5e7772 +size 7189268 diff --git a/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb b/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb index c4635f37d8..35a03d0a08 100644 --- a/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/light-oak-wooden-bed-tray-with-handles-and-short/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7173622522e2e71b78ff95a86a25daedec44d4ccf5c4a979de848e5a0b8ff3bc -size 20488 +oid sha256:3c3bf58548ec8ab5fd698a3fd73104ae64dbeac124cd1806b72dc9e9493474be +size 292404 diff --git a/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb b/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb index 8c35ca7096..876872f3c4 100644 --- a/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/luxury-rainfall-shower-head-and-wall-mounted-mix/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a64851cb4fcdd73606d53aa21c14c429e95014739b1fbcbbc975e6d7c3076dd -size 110392 +oid sha256:f004531170d3176448ad56a98f7d376e32669557fb4f4366c5bb6316aec45201 +size 392756 diff --git a/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb b/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb index 7544e36ec5..fa1f3f5958 100644 --- a/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/matching-ceramic-soap-dispenser-toothbrush-hold/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cbd612bf6d352856f370f4e6fec5593829a5e06ed4a2097e856a1df5703cdb1 -size 123028 +oid sha256:6d535dabf534509fe3e28452975aaa5ac01c6c7e06c7f65686a9f8b483166cde +size 390684 diff --git a/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb b/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb index 372baf0ee2..172c033078 100644 --- a/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/minimalist-floating-wooden-wall-shelves-for-bath/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:447433e32bfc2f6dad6c998df0ff352577e4e35759455b079324c47c17bfc5be -size 66600 +oid sha256:3c372327111290d8e3039ae7214469fa7ca608fa82fa25014d2739bc6964e5dd +size 338516 diff --git a/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb index b9b0b71815..e0797a7ae9 100644 --- a/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/modern-cordless-electric-kettle-with-brushed-met/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3b8641521299e073c3fc8034ae10b23111c96aff801849417f6f2b481c6528f -size 137580 +oid sha256:b6978435381bfb06cb50f5240e3826e7654ddd3d4ae5798224fb8bb35773a58d +size 402256 diff --git a/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb index d11cb0d228..aa78105c80 100644 --- a/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/modern-l-shaped-sectional/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7707ff7cc37565fa00812a279b94f263885b1023bc71eb816dbf13faf9348dbb -size 717952 +oid sha256:c90d01cd485a76c111f479f0adf98edafb94ed9de8412e1a1648366d398f6cb7 +size 1237140 diff --git a/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb index f1adea26e5..3a78d2d0fd 100644 --- a/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/modern-office-work-desk/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c7f2a70785473f91365b98caa526d53a960b320792904f08fb27603338ce06f -size 128712 +oid sha256:e8a0531a082830ed5ae7944298a08d06522ae16e5be4fc4d2297672dcda6580d +size 7261060 diff --git a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb index d8dd2492d4..49cc13911e 100644 --- a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3608c64008626bc1eb78503363aa0688f5baedca9fbdba4aff663602247d467c -size 68484 +oid sha256:5120ee40ab6e16d691bec8df9a1706c342667de183b14eb39d9194c2f9ce5a10 +size 587672 diff --git a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb index b73c480c32..f84aa514c5 100644 --- a/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb +++ b/misc/DimSim/scenes/apartment/objects/modern-tripod-floor-lamp/state-mlrpcv6c-bdvt.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:778ab2cbb680be0b67bce8062f760198879846fa5ff4125f49e115c15abd207b -size 68528 +oid sha256:9c87c9326473786effe27a9c42a3bbe3ac91b495a38de05b2936c907fbc11f50 +size 587716 diff --git a/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb b/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb index f4a8e74740..dba8028cd2 100644 --- a/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/pair-of-plush-white-bed-pillows-with-cotton-text/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4ad78901c92d872372b15693d8160d0ba6d339ce8c1d2d4d00ffde515868617 -size 190364 +oid sha256:7cabb071524c75b9f5127e72b2eb0734a25c99c3b6abe2fb5715a40027b87715 +size 471560 diff --git a/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb b/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb index b0fab3a30c..85b1763dfb 100644 --- a/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/polished-chrome-wall-mounted-towel-rack-with-fol/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01c10671b111d6d1b450291dd002c00ce0458e82fe3a757290492fdd1fab0ba2 -size 240916 +oid sha256:eecd13ed1d667181967fef1e0c5d91acfc0915d76bc7e0404b1bf2b30e849439 +size 514304 diff --git a/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb b/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb index c7ac6e7742..a6d7c7e1dc 100644 --- a/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/queen-size-bed/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b05a0a072c24fa603dd41fa762016b467069123126352c7f5c249698f23c3e9 -size 474460 +oid sha256:b4d911f624cded8e69086cedfbeedff25e92eaf8532bee7e69acc05e50973b5d +size 9431752 diff --git a/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb b/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb index a2e739efa8..8538d93604 100644 --- a/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/salt-and-pepper-shakers/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6006144d35d1273e67ff0536be6d184c41053cb4303262fe0a773f432a3aee2 -size 52228 +oid sha256:80d34c58c3291423efd924ac68e106fcb1f1065243f3ac1f3e670b01a6855a69 +size 612636 diff --git a/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb b/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb index a3a5b282b0..f2cad36004 100644 --- a/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/set-of-three-canvas-prints-featuring-detailed-bo/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b22d1a75f3ea4d66c53bf8d984a1cb5ff507d1cdde687263653655acc44cbad -size 7764 +oid sha256:89f68d3fb22840bac269f57bee966a1b1d0e846befa4f2cc6a1ed966e1606d51 +size 818456 diff --git a/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb b/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb index f9adf371d1..a9dc6cc425 100644 --- a/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/sleek-countertop-microwave-oven-with-digital-dis/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaeaa8161e3af4411aaededfe8fe4772a50ca5db5ff07135a444466f81449e10 -size 143480 +oid sha256:9cc288a2c972526327ec8e7b0bc6562415ecc848b925d86c69b9cc13200dcbbb +size 420836 diff --git a/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb b/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb index d69b029176..48087debc4 100644 --- a/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/small-potted-plant/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8177152a0cbe9cdf89fb2906e146fcbb8ed7fff683bb78cd55a7898bad7fe3d -size 27244 +oid sha256:24fd6882ffcaae0f165c01fd64d95ee3937c0e93fd819af98aefaf0f8a0c5268 +size 303104 diff --git a/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb b/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb index 04856ebb05..57f9601a50 100644 --- a/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/soft-fuzzy-stuffed-teddy-bear-with-button-eyes-a/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fc2e3d0fa90fa8402b96100c1010bc32a96d46ec22b7ab0024a9e111215512f -size 248264 +oid sha256:000e1d838c92196895931bcee3f01bf6543abdb90e31c6339c069765fb0d48bb +size 522336 diff --git a/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb b/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb index 768cc61231..72fe4dc167 100644 --- a/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb +++ b/misc/DimSim/scenes/apartment/objects/television/state-mlr4dvjl-fyw3.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcedd8eb975635630ed9abdce28de8ddecc27237dfea24ecfa12b7ff92f872e9 -size 12516 +oid sha256:046becfcfc027861349c1ffc356cd3eaf83bf3726f05453f90b1b9b5d28ab871 +size 660476 diff --git a/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb b/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb index 625b5fb404..b1edf306f4 100644 --- a/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/thick-quilted-duvet-neatly-folded-with-soft-fabr/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:195eea6a93ae9f816c6d7cd7bc26ca7564225c8dfe11c49c8258d45b0d3d35ee -size 198920 +oid sha256:b312be7201ac8833e1bd5d85679dd4cd420703293ce4d6895640a54b71ffe2cc +size 464536 diff --git a/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb b/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb index 2687710f29..2bffca99a7 100644 --- a/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/thin-glass-and-metal-smartphone-with-reflective/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df968d04ffb906955a0274dd0bcf9969eb73f0bdd4ead3f7180567d2daaf28bf -size 198372 +oid sha256:9bad5b56ef44fc955ff40c968a0a6f094557ae0ab2f8373696c44a63c22eb21a +size 466028 diff --git a/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb b/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb index 143e198581..b66d675704 100644 --- a/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/vintage-poster/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:500b9701f0655995f71c552bae214177226ab8eb46bf85ae8d7b54f962523fcb -size 15524 +oid sha256:084c6f0c20440e05a5b8812faaac3b899bdb6cb8ee1b7820f49d850f02cd6f09 +size 151300 diff --git a/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb b/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb index 6a58f0b629..89ab7cf09d 100644 --- a/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/wooden-bookcase/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17cea02c7e72813f3d3d6545c3e155c16b71ac68dc28c0a74df9c9f00b0c8dc0 -size 16868 +oid sha256:23d924aefb491eb99f8e307b3c019dbd014a3c2afe8e20bbf1c3801a92598bee +size 7149216 diff --git a/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb b/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb index dff460e58c..0fc7514dfb 100644 --- a/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/wooden-coffee-table-with-glass-top/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ebcfd3c2c8fe6ab67ad76d83f52752a6836a49a75a4834c2664586d8ffc378e -size 948808 +oid sha256:e94ca898907df260f15dfa16e06a74b171de86d4d2b35562305714f95a2a023c +size 8081160 diff --git a/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb b/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb index 47f6f6e0f5..5db9b1dbaa 100644 --- a/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb +++ b/misc/DimSim/scenes/apartment/objects/work-chair/state-mlrqdudw-h1ax.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:046b039e29f764963ffcaacacec81f5c54faa7d5055e1e1a95967ab68419b71d -size 869368 +oid sha256:c412550dbdca13a9156bb80c7ad84aa38053518c50c69f4d91a1c387b3883a81 +size 1462112 diff --git a/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb b/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb index d29145807c..aedfef0f45 100644 --- a/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/woven-rattan-placemat/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51c5782524ebd9ee9638e5e9f64f99d1cafc3947e2c3dafe25ff8eed391b3f9e -size 21376 +oid sha256:5d06b94922c52ff1bcc4de4c62c9a48558e4a26af147ad7e59157cac1d7d2b25 +size 285780 diff --git a/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb b/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb index e259eb446c..f22b66f7a7 100644 --- a/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb +++ b/misc/DimSim/scenes/apartment/objects/woven-wicker-laundry-hamper-with-a-fabric-liner/state-default.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1ed7a07c90c81045c0259828cd0e2a2cd19e7ebf5d750b38947a989ade5387e -size 28580 +oid sha256:652a0d297f197925de9cd3861e94185c03ae2f317033b5da01b59a85cc3c6c93 +size 563716 From 87a8c2914b173879d172d244617f1e0150f0a3f9 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Wed, 20 May 2026 14:22:23 -0700 Subject: [PATCH 38/43] remove obsolete one-shot decompose pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cli/export-apartment.ts, cli/export-structure.ts: drivers used once for apartment data → GLB conversion; data files are gone so they can't re-run - engine.js: drop the window.dimsim.exportApartmentAssets/exportStructure functions + helpers (~260 lines) — only the now-deleted drivers called them - bridge/server.ts: drop the /export-asset, /export-manifest, /export-structure POST endpoints — dead without the in-engine exporters - apartment/index.js: trim docstring refs to deleted files --- misc/DimSim/cli/bridge/server.ts | 39 ---- misc/DimSim/cli/export-apartment.ts | 127 ------------- misc/DimSim/cli/export-structure.ts | 173 ----------------- misc/DimSim/scenes/apartment/index.js | 8 +- misc/DimSim/src/engine.js | 259 -------------------------- 5 files changed, 3 insertions(+), 603 deletions(-) delete mode 100644 misc/DimSim/cli/export-apartment.ts delete mode 100644 misc/DimSim/cli/export-structure.ts diff --git a/misc/DimSim/cli/bridge/server.ts b/misc/DimSim/cli/bridge/server.ts index fd7554f8d4..d7c75eb59f 100644 --- a/misc/DimSim/cli/bridge/server.ts +++ b/misc/DimSim/cli/bridge/server.ts @@ -439,45 +439,6 @@ export async function startBridgeServer(options: BridgeServerOptions) { return response; } - // POST /export-asset?name=.glb → write a single GLB - // POST /export-manifest → write objects/manifest.json - // POST /export-structure → write apartment/structure.glb - // Used by the in-browser GLTFExporter pipeline to decompose a scene's - // assets back to disk (see src/engine.js → window.dimsim.exportApartmentAssets). - if (req.method === "POST" && (url.pathname === "/export-asset" || url.pathname === "/export-manifest" || url.pathname === "/export-structure")) { - const writeRoot = Deno.env.get("DIMSIM_SCENES_DIR") || `${distDir}/../scenes`; - const objectsDir = `${writeRoot}/apartment/objects`; - const apartmentDir = `${writeRoot}/apartment`; - if (url.pathname === "/export-structure") { - await Deno.mkdir(apartmentDir, { recursive: true }); - const body = new Uint8Array(await req.arrayBuffer()); - const target = `${apartmentDir}/structure.glb`; - await Deno.writeFile(target, body); - console.log(`[bridge] wrote structure.glb (${(body.byteLength/1024).toFixed(1)} KB)`); - return new Response("ok"); - } - await Deno.mkdir(objectsDir, { recursive: true }); - if (url.pathname === "/export-asset") { - const name = url.searchParams.get("name") || ""; - // Allow one level of nesting: /.glb - if (!/^[a-z0-9][a-z0-9-_]*(?:\/[a-z0-9][a-z0-9-_]*)?\.glb$/.test(name)) { - return new Response("bad name", { status: 400 }); - } - const body = new Uint8Array(await req.arrayBuffer()); - const target = `${objectsDir}/${name}`; - const slash = name.lastIndexOf("/"); - if (slash !== -1) { - await Deno.mkdir(`${objectsDir}/${name.slice(0, slash)}`, { recursive: true }); - } - await Deno.writeFile(target, body); - console.log(`[bridge] wrote ${name} (${(body.byteLength/1024).toFixed(1)} KB)`); - return new Response("ok"); - } - const text = await req.text(); - await Deno.writeTextFile(`${objectsDir}/manifest.json`, text); - console.log(`[bridge] wrote manifest.json`); - return new Response("ok"); - } if (url.pathname === "/" || url.pathname === "/index.html") { try { diff --git a/misc/DimSim/cli/export-apartment.ts b/misc/DimSim/cli/export-apartment.ts deleted file mode 100644 index a99d5efafd..0000000000 --- a/misc/DimSim/cli/export-apartment.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Drives `window.dimsim.exportApartmentAssets()` against a freshly-booted - * apartment scene in headless Chromium. Writes GLBs + manifest.json into - * scenes/apartment/objects/ via the bridge's /export-asset endpoint. - * - * Run: - * # Full decompose: data-driven apartment → GLBs + manifest - * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-apartment.ts - * - * # Verify-only: just load the apartment and confirm assets instantiate - * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-apartment.ts --verify - * - * Requires system Chrome on macOS (bundled Chromium WebGL is broken): - * DIMSIM_CHROME_CHANNEL=chrome (set automatically below) - */ -import { launchHeadless } from "./headless/launcher.ts"; - -const PORT = 8099; -const DIST_DIR = new URL("../dist", import.meta.url).pathname; -const URL_BASE = `http://localhost:${PORT}`; -const VERIFY_ONLY = Deno.args.includes("--verify"); - -// Force system Chrome for WebGL on macOS — bundled Chromium can't render. -if (!Deno.env.get("DIMSIM_CHROME_CHANNEL")) { - Deno.env.set("DIMSIM_CHROME_CHANNEL", "chrome"); -} - -async function waitForUrl(url: string, timeoutMs = 15_000): Promise { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - try { - const res = await fetch(url); - if (res.ok) return; - } catch { /* not up yet */ } - await new Promise((r) => setTimeout(r, 200)); - } - throw new Error(`bridge not ready within ${timeoutMs}ms`); -} - -async function main() { - // 1. Start bridge as subprocess - const bridgeCmd = new Deno.Command("deno", { - args: [ - "run", "--allow-all", "--unstable-net", - new URL("./bridge/server.ts", import.meta.url).pathname, - "--scene", "apartment", - "--port", String(PORT), - ], - stdout: "piped", stderr: "piped", - }); - const bridge = bridgeCmd.spawn(); - // Forward bridge logs so we can see /export-asset writes happen - (async () => { - const dec = new TextDecoder(); - for await (const c of bridge.stdout.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge] ${dec.decode(c)}`)); - })(); - (async () => { - const dec = new TextDecoder(); - for await (const c of bridge.stderr.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge!] ${dec.decode(c)}`)); - })(); - - console.log(`[driver] waiting for bridge on :${PORT}`); - await waitForUrl(`${URL_BASE}/`); - console.log(`[driver] bridge up`); - - // 2. Launch headless chrome + go to apartment - const inst = await launchHeadless({ url: `${URL_BASE}/`, render: "gpu", timeout: 60_000 }); - - try { - // 3. Wait until the apartment finishes instantiating all assets. - // The engine's dimos boot flips a "scene loaded" log after `loadLevel` - // completes; we detect that via the assets[] count growing past 80. - console.log(`[driver] waiting for apartment assets to instantiate`); - await inst.page.waitForFunction( - () => { - const w = window as unknown as { dimsim?: { exportApartmentAssets?: unknown } }; - // assets[] is closed over inside engine.js — exposed indirectly via the - // export helper, which only resolves once `assets` is populated. - if (!w.dimsim || typeof w.dimsim.exportApartmentAssets !== "function") return false; - // Probe: peek at how many asset groups exist in the scene - const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; - if (!scene) return false; - let n = 0; - scene.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); - return n >= 80; - }, - undefined, - { timeout: 60_000, polling: 500 }, - ); - - // Give the engine a bit more time to finish material+texture loads. - await new Promise((r) => setTimeout(r, 3000)); - - if (VERIFY_ONLY) { - const stats = await inst.page.evaluate(() => { - const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; - let n = 0; - scene?.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); - return { assetGroups: n }; - }); - console.log(`[driver] verify: ${stats.assetGroups} asset groups instantiated`); - } else { - console.log(`[driver] running exportApartmentAssets()`); - const result = await inst.page.evaluate(async () => { - const w = window as unknown as { dimsim: { exportApartmentAssets: () => Promise } }; - const m = await w.dimsim.exportApartmentAssets(); - return { count: m.length }; - }); - console.log(`[driver] export returned: ${result.count} asset entries`); - const manifestPath = new URL("../scenes/apartment/objects/manifest.json", import.meta.url).pathname; - const txt = await Deno.readTextFile(manifestPath); - const parsed = JSON.parse(txt); - console.log(`[driver] manifest.json: ${parsed.length} entries`); - const totalStates = parsed.reduce((acc: number, e: { states: unknown[] }) => acc + e.states.length, 0); - console.log(`[driver] total state-glbs: ${totalStates}`); - } - } finally { - await inst.close(); - try { bridge.kill("SIGTERM"); } catch { /* ignore */ } - await bridge.status; - } -} - -if (import.meta.main) { - await main(); - Deno.exit(0); -} diff --git a/misc/DimSim/cli/export-structure.ts b/misc/DimSim/cli/export-structure.ts deleted file mode 100644 index 3eb5be3470..0000000000 --- a/misc/DimSim/cli/export-structure.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * One-shot conversion: bakes the apartment's static-structure primitives - * (data/structure.js) into scenes/apartment/structure.glb via the in-browser - * GLTFExporter. Mirrors cli/export-apartment.ts but boots a temporary - * data-driven index.js (so the primitives[] array is populated in the engine), - * runs window.dimsim.exportStructure(), then restores the GLB-driven index.js. - * - * Run: - * ~/.deno/bin/deno run --allow-all --unstable-net misc/DimSim/cli/export-structure.ts - * - * Requires system Chrome on macOS (bundled Chromium WebGL is broken): - * DIMSIM_CHROME_CHANNEL=chrome (set automatically below) - */ -import { launchHeadless } from "./headless/launcher.ts"; - -const PORT = 8099; -const URL_BASE = `http://localhost:${PORT}`; - -const APARTMENT_INDEX = new URL("../scenes/apartment/index.js", import.meta.url).pathname; -const APARTMENT_INDEX_BACKUP = "/tmp/apartment.index.js.glb-driven"; -const STRUCTURE_GLB = new URL("../scenes/apartment/structure.glb", import.meta.url).pathname; - -// Minimal data-driven build() that populates engine.primitives[] from the -// existing data/structure.js. This is only used transiently during export — -// we restore the real (GLB-driven) index.js at the end. -const DATA_DRIVEN_INDEX = `// TEMP — used by cli/export-structure.ts to bake structure.glb. -// Restored to GLB-driven version automatically after export. -import { SKY } from './data/sky.js'; -import { TAGS } from './data/tags.js'; -import { GROUPS } from './data/groups.js'; -import { LIGHTS } from './data/lights.js'; -import { PRIMITIVES } from './data/structure.js'; -import { ASSETS } from './data/objects.js'; - -export default async function build({ loadLevel }) { - await loadLevel({ - version: '2.0', - worldKey: 'default', - tags: TAGS, - primitives: PRIMITIVES, - assets: ASSETS, - lights: LIGHTS, - groups: GROUPS, - sceneSettings: { sky: SKY }, - }); - - return { - embodiment: null, - spawnPoint: { x: 2, y: 0.5, z: 3 }, - }; -} -`; - -// Force system Chrome for WebGL on macOS — bundled Chromium can't render. -if (!Deno.env.get("DIMSIM_CHROME_CHANNEL")) { - Deno.env.set("DIMSIM_CHROME_CHANNEL", "chrome"); -} - -async function waitForUrl(url: string, timeoutMs = 15_000): Promise { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - try { - const res = await fetch(url); - if (res.ok) return; - } catch { /* not up yet */ } - await new Promise((r) => setTimeout(r, 200)); - } - throw new Error(`bridge not ready within ${timeoutMs}ms`); -} - -async function fileExists(path: string): Promise { - try { - await Deno.stat(path); - return true; - } catch { - return false; - } -} - -async function main() { - // 1. Snapshot the current GLB-driven index.js and swap in a data-driven one. - const originalIndex = await Deno.readTextFile(APARTMENT_INDEX); - await Deno.writeTextFile(APARTMENT_INDEX_BACKUP, originalIndex); - await Deno.writeTextFile(APARTMENT_INDEX, DATA_DRIVEN_INDEX); - console.log(`[driver] swapped apartment/index.js to data-driven mode (backup at ${APARTMENT_INDEX_BACKUP})`); - - let bridge: Deno.ChildProcess | null = null; - try { - // 2. Start bridge as subprocess - const bridgeCmd = new Deno.Command("deno", { - args: [ - "run", "--allow-all", "--unstable-net", - new URL("./bridge/server.ts", import.meta.url).pathname, - "--scene", "apartment", - "--port", String(PORT), - ], - stdout: "piped", stderr: "piped", - }); - bridge = bridgeCmd.spawn(); - (async () => { - const dec = new TextDecoder(); - for await (const c of bridge!.stdout.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge] ${dec.decode(c)}`)); - })(); - (async () => { - const dec = new TextDecoder(); - for await (const c of bridge!.stderr.values()) Deno.stdout.write(new TextEncoder().encode(`[bridge!] ${dec.decode(c)}`)); - })(); - - console.log(`[driver] waiting for bridge on :${PORT}`); - await waitForUrl(`${URL_BASE}/`); - console.log(`[driver] bridge up`); - - // 3. Launch headless chrome + go to apartment - const inst = await launchHeadless({ url: `${URL_BASE}/`, render: "gpu", timeout: 60_000 }); - - try { - // 4. Wait until the apartment finishes instantiating all assets — same - // gate as export-apartment.ts. Once asset groups are in the scene, - // the engine's primitives[] is also populated. - console.log(`[driver] waiting for apartment to finish loading`); - await inst.page.waitForFunction( - () => { - const w = window as unknown as { dimsim?: { exportStructure?: unknown } }; - if (!w.dimsim || typeof w.dimsim.exportStructure !== "function") return false; - const scene = (window as unknown as { __dimsim?: { scene?: { traverse: (cb: (o: { name?: string }) => void) => void } } }).__dimsim?.scene; - if (!scene) return false; - let n = 0; - scene.traverse((o) => { if (o.name && o.name.startsWith("asset:")) n += 1; }); - return n >= 80; - }, - undefined, - { timeout: 60_000, polling: 500 }, - ); - - // Give the engine a bit more time to finish material+texture loads. - await new Promise((r) => setTimeout(r, 3000)); - - console.log(`[driver] running exportStructure()`); - const result = await inst.page.evaluate(async () => { - const w = window as unknown as { dimsim: { exportStructure: () => Promise<{ bytes: number; count: number }> } }; - return await w.dimsim.exportStructure(); - }); - console.log(`[driver] export returned: ${result.count} primitives, ${(result.bytes/1024).toFixed(1)} KB`); - } finally { - await inst.close(); - } - } finally { - // 5. Always restore the GLB-driven index.js, even on error. - try { - const backup = await Deno.readTextFile(APARTMENT_INDEX_BACKUP); - await Deno.writeTextFile(APARTMENT_INDEX, backup); - console.log(`[driver] restored apartment/index.js from backup`); - } catch (e) { - console.error(`[driver] FAILED to restore index.js (backup still at ${APARTMENT_INDEX_BACKUP}):`, e); - } - if (bridge) { - try { bridge.kill("SIGTERM"); } catch { /* ignore */ } - await bridge.status; - } - } - - // 6. Verify the GLB landed on disk. - if (!(await fileExists(STRUCTURE_GLB))) { - throw new Error(`structure.glb was not written to ${STRUCTURE_GLB}`); - } - const stat = await Deno.stat(STRUCTURE_GLB); - console.log(`[driver] OK: ${STRUCTURE_GLB} (${(stat.size/1024).toFixed(1)} KB)`); -} - -if (import.meta.main) { - await main(); - Deno.exit(0); -} diff --git a/misc/DimSim/scenes/apartment/index.js b/misc/DimSim/scenes/apartment/index.js index f940866399..215994f64f 100644 --- a/misc/DimSim/scenes/apartment/index.js +++ b/misc/DimSim/scenes/apartment/index.js @@ -1,10 +1,8 @@ // scenes/apartment/index.js — pure Three.js entry for the apartment. // -// Static structure (walls, floor, ceiling, fixtures) lives in ./structure.glb, -// baked once from the original primitives via cli/export-structure.ts. -// Interactive objects (fridge, cabinets, TV, etc.) live as GLB-per-state files -// under ./objects/, generated by cli/export-apartment.ts. Sky, lights, and tags -// are inlined here so the scene is fully imperative — no data/ files needed. +// Static structure (walls, floor, ceiling, fixtures): ./structure.glb +// Interactive objects (fridge, cabinets, TV, etc.): ./objects//.glb +// keyed by ./objects/manifest.json. Sky, lights, tags inlined below. const SKY = { topColor: '#0b64f4', diff --git a/misc/DimSim/src/engine.js b/misc/DimSim/src/engine.js index 708d72ce0e..221a29e9eb 100644 --- a/misc/DimSim/src/engine.js +++ b/misc/DimSim/src/engine.js @@ -7682,262 +7682,3 @@ if (dimosMode) { })(); } -// ── Apartment-decompose pipeline ───────────────────────────────────────────── -// Drives Three.js's own GLTFExporter against every state of every asset and -// POSTs the .glb bytes to the bridge (cli/bridge/server.ts → /export-asset). -// One GLB per (asset, state) pair, so multi-state objects (fridge open/closed, -// cabinet drawers, gas-range knobs) keep their state machine when the apartment -// reloads from GLBs. The manifest carries asset-level metadata — transform, -// _shapePivotCenter, actions, pickable, bumpable, currentStateId — so the -// loader rebuilds full behavior, not just visuals. -// -// Usage from devtools after apartment loads: -// await dimsim.exportApartmentAssets() // all assets, all states -// await dimsim.exportApartmentAssets({ limit: 1 }) // first asset — smoke test -// await dimsim.exportApartmentAsset('id-or-title') // one by name/id -function _slugifyForGlb(s) { - return String(s || "asset") - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, "") - .slice(0, 80) || "asset"; -} -async function _waitTexturesLoaded(root) { - const texSlots = [ - "map", "normalMap", "roughnessMap", "metalnessMap", "aoMap", "emissiveMap", - "bumpMap", "displacementMap", "specularColorMap", "clearcoatMap", - "clearcoatNormalMap", "clearcoatRoughnessMap", "sheenColorMap", - "sheenRoughnessMap", "transmissionMap", "thicknessMap", "iridescenceMap", - "iridescenceThicknessMap", - ]; - const textures = new Set(); - root.traverse((node) => { - if (!node.isMesh) return; - const mats = Array.isArray(node.material) ? node.material : node.material ? [node.material] : []; - for (const m of mats) { - for (const key of texSlots) if (m && m[key]) textures.add(m[key]); - } - }); - await Promise.all([...textures].map((tex) => new Promise((resolve) => { - const ready = () => !!(tex.image && (tex.image.complete !== false) && (tex.image.width || tex.image.naturalWidth)); - if (ready()) return resolve(); - const start = Date.now(); - const tick = () => { - if (ready() || Date.now() - start > 10000) return resolve(); - setTimeout(tick, 50); - }; - tick(); - }))); -} -async function _exportAssetAllStates(asset, exporter, assetSlugTaken, libCache) { - if (!asset?.states?.length) return null; - - // Storage dedup: if this asset shares libraryAssetId with one we've already - // exported, reuse those state GLB files. Manifest still gets its own entry - // per placed instance (each carries instance-specific transform, action IDs, - // and state IDs), but the `file` paths point to the shared library asset. - const libId = asset.libraryAssetId; - if (libId && libCache && libCache.has(libId)) { - const cached = libCache.get(libId); - const stateEntries = asset.states.map((s, i) => ({ - id: s.id, - name: s.name || s.id, - // Match by state index (instances of the same library have parallel state arrays). - file: cached.stateFiles[i] || cached.stateFiles[0], - })); - return { - id: asset.id, - title: asset.title || null, - transform: asset.transform || null, - currentStateId: asset.currentStateId || stateEntries[0].id, - _shapePivotCenter: asset._shapePivotCenter || null, - pickable: asset.pickable === true, - bumpable: asset.bumpable === true, - bumpResponse: asset.bumpResponse, - bumpDamping: asset.bumpDamping, - castShadow: asset.castShadow === true, - receiveShadow: asset.receiveShadow === true, - actions: Array.isArray(asset.actions) ? asset.actions : [], - states: stateEntries, - _bytes: 0, // already counted on the first export - _libRef: libId, - }; - } - - // Stable asset slug (collision-free across the run) - let assetSlug = _slugifyForGlb(asset.title || asset.id); - let n = 1; - while (assetSlugTaken.has(assetSlug)) { n += 1; assetSlug = `${_slugifyForGlb(asset.title || asset.id)}-${n}`; } - assetSlugTaken.add(assetSlug); - - // Use the same pivot the engine baked in so every state lines up with the - // asset's transform.position (which is the pivot in world space). - const pivot = asset._shapePivotCenter - ? new THREE.Vector3(asset._shapePivotCenter.x, asset._shapePivotCenter.y, asset._shapePivotCenter.z) - : null; - - const stateEntries = []; - let totalBytes = 0; - const stateSlugTaken = new Set(); - - for (const state of asset.states) { - let stateSlug = _slugifyForGlb(state.id || state.name || "state"); - let k = 1; - while (stateSlugTaken.has(stateSlug)) { k += 1; stateSlug = `${_slugifyForGlb(state.id || state.name || "state")}-${k}`; } - stateSlugTaken.add(stateSlug); - const file = `${assetSlug}/${stateSlug}.glb`; - - try { - // Build a fresh state root via the same path instantiateAsset uses, - // independent of which state is currently visible in the scene. - const root = buildShapeStateRoot(state, asset.id, pivot); - // Local-space GLB: zero the root so children sit relative to the asset - // origin (apartment.transform will be applied on load). - root.position.set(0, 0, 0); - root.rotation.set(0, 0, 0); - root.scale.set(1, 1, 1); - root.updateMatrixWorld(true); - - // TextureLoader.load is async; wait for images to actually decode before - // GLTFExporter inspects them. Otherwise "No valid image data" on any - // primitive whose textureDataUrl fetch hasn't completed yet. - await _waitTexturesLoaded(root); - - const buf = await exporter.parseAsync(root, { binary: true, embedImages: true }); - disposeShapeStateRoot(root); - - const res = await fetch(`/export-asset?name=${encodeURIComponent(file)}`, { - method: "POST", body: buf, headers: { "content-type": "model/gltf-binary" }, - }); - if (!res.ok) { console.warn(`[export] ${file} failed: ${res.status}`); continue; } - totalBytes += buf.byteLength; - stateEntries.push({ id: state.id, name: state.name || state.id, file }); - } catch (e) { - console.error(`[export] ${asset.title || asset.id} state "${state.name || state.id}" failed`, e); - } - } - - if (stateEntries.length === 0) return null; - - // Record this library asset's state files so future placed instances of - // the same library reference these files instead of re-exporting bytes. - if (libId && libCache) { - libCache.set(libId, { stateFiles: stateEntries.map((e) => e.file) }); - } - - return { - id: asset.id, - title: asset.title || null, - transform: asset.transform || null, - currentStateId: asset.currentStateId || stateEntries[0].id, - _shapePivotCenter: asset._shapePivotCenter || null, - pickable: asset.pickable === true, - bumpable: asset.bumpable === true, - bumpResponse: asset.bumpResponse, - bumpDamping: asset.bumpDamping, - castShadow: asset.castShadow === true, - receiveShadow: asset.receiveShadow === true, - actions: Array.isArray(asset.actions) ? asset.actions : [], - states: stateEntries, - _bytes: totalBytes, - _libRef: null, - }; -} -window.dimsim = window.dimsim || {}; -window.dimsim.exportApartmentAssets = async function ({ limit = null, writeManifest = true } = {}) { - const { GLTFExporter } = await import("three/examples/jsm/exporters/GLTFExporter.js"); - const exporter = new GLTFExporter(); - const list = (Array.isArray(assets) ? assets : []).filter((a) => a && a.id && a.states?.length); - const sliced = limit != null ? list.slice(0, limit) : list; - console.log(`[export] starting: ${sliced.length} asset(s), all states`); - const manifest = []; - const taken = new Set(); - const libCache = new Map(); // libraryAssetId → { stateFiles } - let idx = 0; - let dedupedCount = 0; - let totalBytesWritten = 0; - for (const a of sliced) { - idx += 1; - try { - const entry = await _exportAssetAllStates(a, exporter, taken, libCache); - if (entry) { - const { _bytes, _libRef, ...keep } = entry; - manifest.push(keep); - if (_libRef) { - dedupedCount += 1; - console.log(`[export] ${idx}/${sliced.length} ${a.title || a.id} reused library ${_libRef}`); - } else { - totalBytesWritten += _bytes; - console.log(`[export] ${idx}/${sliced.length} ${a.title || a.id} ${entry.states.length} state(s), ${(_bytes/1024).toFixed(1)} KB`); - } - } else { - console.warn(`[export] ${idx}/${sliced.length} skipped: ${a.id}`); - } - } catch (e) { - console.error(`[export] ${idx}/${sliced.length} ${a.title || a.id} failed`, e); - } - } - console.log(`[export] ${manifest.length} entries, ${dedupedCount} deduped (library refs), ${(totalBytesWritten/1024/1024).toFixed(1)} MB written`); - if (writeManifest && manifest.length > 0) { - await fetch("/export-manifest", { - method: "POST", body: JSON.stringify(manifest, null, 2), - headers: { "content-type": "application/json" }, - }); - } - console.log(`[export] done: ${manifest.length}/${sliced.length}`); - return manifest; -}; -window.dimsim.exportApartmentAsset = async function (idOrTitle) { - const list = Array.isArray(assets) ? assets : []; - const needle = String(idOrTitle).toLowerCase(); - const a = list.find((x) => x.id === idOrTitle) || - list.find((x) => (x.title || "").toLowerCase() === needle); - if (!a) { console.error(`[export] no asset matching "${idOrTitle}"`); return null; } - const { GLTFExporter } = await import("three/examples/jsm/exporters/GLTFExporter.js"); - const exporter = new GLTFExporter(); - const entry = await _exportAssetAllStates(a, exporter, new Set()); - if (entry) { - const { _bytes, ...rest } = entry; - console.log(`[export] ${a.title || a.id}: ${entry.states.length} state(s), ${(_bytes/1024).toFixed(1)} KB`); - return rest; - } - return null; -}; -// Bakes the apartment's static-structure primitives (walls, floor, ceiling, -// fixtures — everything in data/structure.js) into a single GLB on disk via -// the bridge's /export-structure endpoint. Used once during the conversion -// from data-driven primitives to a pure-Three.js scene module. -window.dimsim.exportStructure = async function () { - const { GLTFExporter } = await import("three/examples/jsm/exporters/GLTFExporter.js"); - const exporter = new GLTFExporter(); - const root = new THREE.Group(); - root.name = "apartment-structure"; - for (const p of primitives) { - const geom = createPrimitiveGeometry(p.type, p.dimensions || {}); - const mat = createPrimitiveMaterial(p.material || {}); - const mesh = new THREE.Mesh(geom, mat); - mesh.name = p.id || p.name || "primitive"; - const tr = p.transform || {}; - if (tr.position) mesh.position.set(tr.position.x ?? 0, tr.position.y ?? 0, tr.position.z ?? 0); - if (tr.rotation) mesh.rotation.set(tr.rotation.x ?? 0, tr.rotation.y ?? 0, tr.rotation.z ?? 0); - if (tr.scale) mesh.scale.set(tr.scale.x ?? 1, tr.scale.y ?? 1, tr.scale.z ?? 1); - mesh.castShadow = p.castShadow === true; - mesh.receiveShadow = p.receiveShadow !== false; - root.add(mesh); - } - root.updateMatrixWorld(true); - await _waitTexturesLoaded(root); - const buf = await exporter.parseAsync(root, { binary: true, embedImages: true }); - const res = await fetch("/export-structure", { - method: "POST", body: buf, headers: { "content-type": "model/gltf-binary" }, - }); - if (!res.ok) throw new Error(`export-structure failed: ${res.status}`); - console.log(`[export-structure] wrote ${(buf.byteLength/1024).toFixed(1)} KB (${primitives.length} primitives)`); - root.traverse((o) => { - if (!o.isMesh) return; - o.geometry?.dispose?.(); - const mats = Array.isArray(o.material) ? o.material : (o.material ? [o.material] : []); - for (const m of mats) m?.dispose?.(); - }); - return { bytes: buf.byteLength, count: primitives.length }; -}; From 0a5b661a02a07408b9bb61cfdf09370d68b61e14 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Wed, 20 May 2026 14:50:12 -0700 Subject: [PATCH 39/43] fix LFS rule for embodiment/ folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier rename of public/agent-model/ → public/embodiment/ updated the folder + references but the .gitattributes LFS rule got lost in the rebase. Point the rule at the new path so future GLBs added there are tracked via LFS. --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 583a7a5848..0c2c7b4217 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,6 +20,6 @@ docs/capabilities/memory/assets/** filter=lfs diff=lfs merge=lfs -text docs/capabilities/memory/assets/.gitattributes -filter -diff -merge text # DimSim scene data and agent model misc/DimSim/public/sims/*.json filter=lfs diff=lfs merge=lfs -text -misc/DimSim/public/agent-model/*.glb filter=lfs diff=lfs merge=lfs -text +misc/DimSim/public/embodiment/*.glb filter=lfs diff=lfs merge=lfs -text misc/DimSim/scenes/**/*.glb filter=lfs diff=lfs merge=lfs -text misc/DimSim/scenes/**/*.gltf filter=lfs diff=lfs merge=lfs -text From a1f416291552b14bb6f865bd206dbc744c072e2c Mon Sep 17 00:00:00 2001 From: Viswajit <39920874+Viswa4599@users.noreply.github.com> Date: Wed, 20 May 2026 17:57:23 -0400 Subject: [PATCH 40/43] Potential fix for pull request finding 'CodeQL / Binding a socket to all network interfaces' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- misc/DimSim/cli/test/dimos_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/DimSim/cli/test/dimos_integration.py b/misc/DimSim/cli/test/dimos_integration.py index 1fd8a1abfe..4981dbbe33 100755 --- a/misc/DimSim/cli/test/dimos_integration.py +++ b/misc/DimSim/cli/test/dimos_integration.py @@ -85,7 +85,7 @@ def create_mcast_recv_socket() -> socket.socket: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except AttributeError: pass - sock.bind(("", MCAST_PORT)) + sock.bind(("127.0.0.1", MCAST_PORT)) mreq = struct.pack("4s4s", socket.inet_aton(MCAST_GRP), socket.inet_aton("0.0.0.0")) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0) From b59d121443650d3591f39c55a92d3031c4a3fd2f Mon Sep 17 00:00:00 2001 From: Viswajit <39920874+Viswa4599@users.noreply.github.com> Date: Wed, 20 May 2026 17:58:28 -0400 Subject: [PATCH 41/43] Potential fix for pull request finding 'CodeQL / Use of externally-controlled format string' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- misc/DimSim/evals/harness.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/DimSim/evals/harness.ts b/misc/DimSim/evals/harness.ts index ae18594c0e..188f0abe0c 100644 --- a/misc/DimSim/evals/harness.ts +++ b/misc/DimSim/evals/harness.ts @@ -202,7 +202,7 @@ export class EvalHarness { const cacheBust = `?t=${Date.now()}`; await import(/* @vite-ignore */ workflowUrl + cacheBust); } catch (e: any) { - console.error(`[eval] failed to import ${workflowUrl}:`, e); + console.error("[eval] failed to import %s:", workflowUrl, e); this._send({ type: "evalResult", workflowUrl, scene: "", task: "", passed: false, reason: `import failed: ${e?.message ?? e}`, From c040c59527f7520434a53f9142d0b6c066dae4d8 Mon Sep 17 00:00:00 2001 From: Viswajit Nair Date: Wed, 20 May 2026 15:09:45 -0700 Subject: [PATCH 42/43] fix: address greptile review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cli/cli.ts: `dimsim dev` --scene default "apt" → "apartment" so the default fallback finds the scene module instead of 404'ing - bridge/server.ts: sanitize --scene against [a-zA-Z0-9_-]+ before interpolating into the